Prechádzať zdrojové kódy

revert: undo prompt softening and compaction (iter-002 regression fix)

Reverts 822708e, 4248e1d, eb288b3 which:
- Softened all directive language from "must/do not" to "try/prefer"
- Compressed writer context into compact Story Briefs
- Removed selectedEvidenceBlock from governed mode
- Extended compact briefs to auditor/reviser

Control experiment showed these 3 commits are the primary regression
suspects: iter-001 build + gpt-5.4 scored 75.5/66.5, while iter-002
(with these commits) scored 71.5/37.5 on the same model.

Restores imperative directive language and full context sections.
Ma 7 mesiacov pred
rodič
commit
86ea01a2a3

+ 1 - 2
packages/core/src/__tests__/chapter-analyzer.test.ts

@@ -561,8 +561,7 @@ describe("ChapterAnalyzerAgent", () => {
       const messages = chat.mock.calls[0]?.[0] as Array<{ role: string; content: string }>;
       const messages = chat.mock.calls[0]?.[0] as Array<{ role: string; content: string }>;
       const userPrompt = messages[1]?.content ?? "";
       const userPrompt = messages[1]?.content ?? "";
 
 
-      expect(userPrompt).toContain("## Analysis Brief");
-      expect(userPrompt).not.toContain("## Chapter Control Inputs (compiled by Planner/Composer)");
+      expect(userPrompt).toContain("## Chapter Control Inputs (compiled by Planner/Composer)");
       expect(userPrompt).toContain("story/pending_hooks.md#mentor-oath");
       expect(userPrompt).toContain("story/pending_hooks.md#mentor-oath");
       expect(userPrompt).toContain("Selected Hook Evidence");
       expect(userPrompt).toContain("Selected Hook Evidence");
       expect(userPrompt).not.toContain("## Story Bible");
       expect(userPrompt).not.toContain("## Story Bible");

+ 0 - 2
packages/core/src/__tests__/continuity.test.ts

@@ -281,8 +281,6 @@ describe("ContinuityAuditor", () => {
         | undefined;
         | undefined;
       const userPrompt = messages?.[1]?.content ?? "";
       const userPrompt = messages?.[1]?.content ?? "";
 
 
-      expect(userPrompt).toContain("## Review Brief");
-      expect(userPrompt).not.toContain("## Chapter Control Inputs (compiled by Planner/Composer)");
       expect(userPrompt).toContain("story/chapter_summaries.md#99");
       expect(userPrompt).toContain("story/chapter_summaries.md#99");
       expect(userPrompt).toContain("story/pending_hooks.md#mentor-oath");
       expect(userPrompt).toContain("story/pending_hooks.md#mentor-oath");
       expect(userPrompt).not.toContain("| 1 | Guild Trail |");
       expect(userPrompt).not.toContain("| 1 | Guild Trail |");

+ 0 - 2
packages/core/src/__tests__/planner.test.ts

@@ -370,8 +370,6 @@ describe("PlannerAgent", () => {
     expect(result.intent.moodDirective).toBeDefined();
     expect(result.intent.moodDirective).toBeDefined();
     expect(result.intent.moodDirective).toContain("降调");
     expect(result.intent.moodDirective).toContain("降调");
     expect(result.intent.moodDirective).toContain("日常");
     expect(result.intent.moodDirective).toContain("日常");
-    expect(result.intent.moodDirective).toContain("建议");
-    expect(result.intent.moodDirective).not.toContain("必须");
   });
   });
 
 
   it("does not emit a mood directive when recent moods are varied", async () => {
   it("does not emit a mood directive when recent moods are varied", async () => {

+ 2 - 6
packages/core/src/__tests__/reviser.test.ts

@@ -453,14 +453,10 @@ describe("ReviserAgent", () => {
         | undefined;
         | undefined;
       const userPrompt = messages?.[1]?.content ?? "";
       const userPrompt = messages?.[1]?.content ?? "";
 
 
-      expect(userPrompt).toContain("## Revision Brief");
-      expect(userPrompt).not.toContain("## 本章控制输入(由 Planner/Composer 编译)");
       expect(userPrompt).toContain("story/chapter_summaries.md#99");
       expect(userPrompt).toContain("story/chapter_summaries.md#99");
       expect(userPrompt).toContain("story/pending_hooks.md#mentor-oath");
       expect(userPrompt).toContain("story/pending_hooks.md#mentor-oath");
-      expect(userPrompt).toContain("The jade seal cannot be destroyed.");
-      expect(userPrompt).toContain("Track the mentor oath fallout.");
-      expect(userPrompt).not.toContain("story/story_bible.md");
-      expect(userPrompt).not.toContain("story/volume_outline.md");
+      expect(userPrompt).toContain("story/story_bible.md");
+      expect(userPrompt).toContain("story/volume_outline.md");
       expect(userPrompt).not.toContain("| 1 | Guild Trail |");
       expect(userPrompt).not.toContain("| 1 | Guild Trail |");
       expect(userPrompt).not.toContain("guild-route | 1 | mystery");
       expect(userPrompt).not.toContain("guild-route | 1 | mystery");
       expect(userPrompt).not.toContain("Guildmaster Ren secretly forged the harbor roster in chapter 140.");
       expect(userPrompt).not.toContain("Guildmaster Ren secretly forged the harbor roster in chapter 140.");

+ 6 - 15
packages/core/src/__tests__/writer.test.ts

@@ -226,9 +226,8 @@ describe("WriterAgent", () => {
       });
       });
 
 
       const settlePrompt = (chatSpy.mock.calls[2]?.[0] as ReadonlyArray<{ content: string }> | undefined)?.[1]?.content ?? "";
       const settlePrompt = (chatSpy.mock.calls[2]?.[0] as ReadonlyArray<{ content: string }> | undefined)?.[1]?.content ?? "";
-      expect(settlePrompt).toContain("## 结算焦点");
-      expect(settlePrompt).not.toContain("### 已选上下文");
-      expect(settlePrompt).not.toContain("## Hook Agenda");
+      expect(settlePrompt).toContain("## 本章控制输入");
+      expect(settlePrompt).toContain("story/chapter_summaries.md#99");
       expect(settlePrompt).toContain("| 99 | Locked Gate |");
       expect(settlePrompt).toContain("| 99 | Locked Gate |");
       expect(settlePrompt).toContain("## Hook Debt Briefs");
       expect(settlePrompt).toContain("## Hook Debt Briefs");
       expect(settlePrompt).toContain("mentor-oath | cadence: slow-burn");
       expect(settlePrompt).toContain("mentor-oath | cadence: slow-burn");
@@ -904,12 +903,11 @@ describe("WriterAgent", () => {
       });
       });
 
 
       const creativePrompt = (chatSpy.mock.calls[0]?.[0] as ReadonlyArray<{ content: string }> | undefined)?.[1]?.content ?? "";
       const creativePrompt = (chatSpy.mock.calls[0]?.[0] as ReadonlyArray<{ content: string }> | undefined)?.[1]?.content ?? "";
-      expect(creativePrompt).toContain("## Story Brief");
-      expect(creativePrompt).not.toContain("## Recent Title History");
+      expect(creativePrompt).toContain("## Recent Title History");
       expect(creativePrompt).toContain("Ledger in Rain");
       expect(creativePrompt).toContain("Ledger in Rain");
-      expect(creativePrompt).not.toContain("## Recent Mood / Chapter Type Trail");
+      expect(creativePrompt).toContain("## Recent Mood / Chapter Type Trail");
       expect(creativePrompt).toContain("tight / investigation");
       expect(creativePrompt).toContain("tight / investigation");
-      expect(creativePrompt).not.toContain("## Canon Evidence");
+      expect(creativePrompt).toContain("## Canon Evidence");
       expect(creativePrompt).toContain("archive fire until volume two");
       expect(creativePrompt).toContain("archive fire until volume two");
       expect(creativePrompt).toContain("oath debt logic must stay intact");
       expect(creativePrompt).toContain("oath debt logic must stay intact");
     } finally {
     } finally {
@@ -1055,14 +1053,7 @@ describe("WriterAgent", () => {
 
 
       expect(systemPrompt).not.toContain("Hook-A / Hook-B");
       expect(systemPrompt).not.toContain("Hook-A / Hook-B");
       expect(systemPrompt).toContain("真实 hook_id");
       expect(systemPrompt).toContain("真实 hook_id");
-      expect(systemPrompt).toContain("preferred narrative response");
-      expect(systemPrompt).not.toContain("follow the requested move");
-      expect(creativePrompt).toContain("## Story Brief");
-      expect(creativePrompt).not.toContain("## Hook Pressure and Debt");
-      expect(creativePrompt).not.toContain("## Selected Context");
-      expect(creativePrompt).not.toContain("### Pressure Map");
-      expect(creativePrompt).not.toContain("## Pending Hooks Snapshot");
-      expect(creativePrompt).not.toContain("## Chapter Summaries Snapshot");
+      expect(creativePrompt).toContain("## Explicit Hook Agenda");
       expect(creativePrompt).toContain("mentor-oath");
       expect(creativePrompt).toContain("mentor-oath");
       expect(creativePrompt).toContain("ledger-fragment");
       expect(creativePrompt).toContain("ledger-fragment");
       expect(creativePrompt).toContain("stale-ledger");
       expect(creativePrompt).toContain("stale-ledger");

+ 45 - 8
packages/core/src/agents/chapter-analyzer.ts

@@ -12,7 +12,6 @@ import {
 import { filterEmotionalArcs, filterSubplots } from "../utils/context-filter.js";
 import { filterEmotionalArcs, filterSubplots } from "../utils/context-filter.js";
 import { countChapterLength, resolveLengthCountingMode } from "../utils/length-metrics.js";
 import { countChapterLength, resolveLengthCountingMode } from "../utils/length-metrics.js";
 import { retrieveMemorySelection } from "../utils/memory-retrieval.js";
 import { retrieveMemorySelection } from "../utils/memory-retrieval.js";
-import { buildProcessBrief } from "../utils/compiled-control-brief.js";
 import { readFile } from "node:fs/promises";
 import { readFile } from "node:fs/promises";
 import { join } from "node:path";
 import { join } from "node:path";
 
 
@@ -96,13 +95,7 @@ export class ChapterAnalyzerAgent extends BaseAgent {
         })
         })
       : characterMatrix;
       : characterMatrix;
     const reducedControlBlock = governedMode && input.chapterIntent && input.contextPackage && input.ruleStack
     const reducedControlBlock = governedMode && input.chapterIntent && input.contextPackage && input.ruleStack
-      ? `\n${buildProcessBrief({
-          kind: "analysis",
-          chapterIntent: input.chapterIntent,
-          contextPackage: input.contextPackage,
-          ruleStack: input.ruleStack,
-          language: resolvedLanguage,
-        })}\n`
+      ? this.buildReducedControlBlock(input.chapterIntent, input.contextPackage, input.ruleStack, resolvedLanguage)
       : "";
       : "";
 
 
     const systemPrompt = this.buildSystemPrompt(
     const systemPrompt = this.buildSystemPrompt(
@@ -468,6 +461,50 @@ ${params.hooksBlock}${params.volumeSummariesBlock}${params.subplotBlock}${params
 请严格按照 === TAG === 格式输出分析结果。`;
 请严格按照 === TAG === 格式输出分析结果。`;
   }
   }
 
 
+  private buildReducedControlBlock(
+    chapterIntent: string,
+    contextPackage: ContextPackage,
+    ruleStack: RuleStack,
+    language: "zh" | "en",
+  ): string {
+    const selectedContext = contextPackage.selectedContext
+      .map((entry) => `- ${entry.source}: ${entry.reason}${entry.excerpt ? ` | ${entry.excerpt}` : ""}`)
+      .join("\n");
+    const overrides = ruleStack.activeOverrides.length > 0
+      ? ruleStack.activeOverrides
+        .map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
+        .join("\n")
+      : "- none";
+
+    return language === "en"
+      ? `\n## Chapter Control Inputs (compiled by Planner/Composer)
+${chapterIntent}
+
+### Selected Context
+${selectedContext || "- none"}
+
+### Rule Stack
+- Hard guardrails: ${ruleStack.sections.hard.join(", ") || "(none)"}
+- Soft constraints: ${ruleStack.sections.soft.join(", ") || "(none)"}
+- Diagnostic rules: ${ruleStack.sections.diagnostic.join(", ") || "(none)"}
+
+### Active Overrides
+${overrides}\n`
+      : `\n## 本章控制输入(由 Planner/Composer 编译)
+${chapterIntent}
+
+### 已选上下文
+${selectedContext || "- none"}
+
+### 规则栈
+- 硬护栏:${ruleStack.sections.hard.join("、") || "(无)"}
+- 软约束:${ruleStack.sections.soft.join("、") || "(无)"}
+- 诊断规则:${ruleStack.sections.diagnostic.join("、") || "(无)"}
+
+### 当前覆盖
+${overrides}\n`;
+  }
+
   private buildMemoryGoal(chapterTitle: string | undefined, chapterContent: string): string {
   private buildMemoryGoal(chapterTitle: string | undefined, chapterContent: string): string {
     return [chapterTitle ?? "", chapterContent.slice(0, 1500)]
     return [chapterTitle ?? "", chapterContent.slice(0, 1500)]
       .filter((part) => part.trim().length > 0)
       .filter((part) => part.trim().length > 0)

+ 45 - 8
packages/core/src/agents/continuity.ts

@@ -8,7 +8,6 @@ import { getFanficDimensionConfig, FANFIC_DIMENSIONS } from "./fanfic-dimensions
 import { readFile, readdir } from "node:fs/promises";
 import { readFile, readdir } from "node:fs/promises";
 import { filterHooks, filterSummaries, filterSubplots, filterEmotionalArcs, filterCharacterMatrix } from "../utils/context-filter.js";
 import { filterHooks, filterSummaries, filterSubplots, filterEmotionalArcs, filterCharacterMatrix } from "../utils/context-filter.js";
 import { buildGovernedMemoryEvidenceBlocks } from "../utils/governed-context.js";
 import { buildGovernedMemoryEvidenceBlocks } from "../utils/governed-context.js";
-import { buildProcessBrief } from "../utils/compiled-control-brief.js";
 import { join } from "node:path";
 import { join } from "node:path";
 
 
 export interface AuditResult {
 export interface AuditResult {
@@ -499,13 +498,7 @@ ${dimList}
         : `\n## 卷纲(用于大纲偏离检测)\n${volumeOutline}\n`
         : `\n## 卷纲(用于大纲偏离检测)\n${volumeOutline}\n`
       : "";
       : "";
     const reducedControlBlock = options?.chapterIntent && options.contextPackage && options.ruleStack
     const reducedControlBlock = options?.chapterIntent && options.contextPackage && options.ruleStack
-      ? `\n${buildProcessBrief({
-          kind: "review",
-          chapterIntent: options.chapterIntent,
-          contextPackage: options.contextPackage,
-          ruleStack: options.ruleStack,
-          language: resolvedLanguage,
-        })}\n`
+      ? this.buildReducedControlBlock(options.chapterIntent, options.contextPackage, options.ruleStack, resolvedLanguage)
       : "";
       : "";
     const styleGuideBlock = reducedControlBlock.length === 0
     const styleGuideBlock = reducedControlBlock.length === 0
       ? isEnglish
       ? isEnglish
@@ -625,6 +618,50 @@ ${chapterContent}`;
     };
     };
   }
   }
 
 
+  private buildReducedControlBlock(
+    chapterIntent: string,
+    contextPackage: ContextPackage,
+    ruleStack: RuleStack,
+    language: PromptLanguage,
+  ): string {
+    const selectedContext = contextPackage.selectedContext
+      .map((entry) => `- ${entry.source}: ${entry.reason}${entry.excerpt ? ` | ${entry.excerpt}` : ""}`)
+      .join("\n");
+    const overrides = ruleStack.activeOverrides.length > 0
+      ? ruleStack.activeOverrides
+        .map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
+        .join("\n")
+      : "- none";
+
+    return language === "en"
+      ? `\n## Chapter Control Inputs (compiled by Planner/Composer)
+${chapterIntent}
+
+### Selected Context
+${selectedContext || "- none"}
+
+### Rule Stack
+- Hard guardrails: ${ruleStack.sections.hard.join(", ") || "(none)"}
+- Soft constraints: ${ruleStack.sections.soft.join(", ") || "(none)"}
+- Diagnostic rules: ${ruleStack.sections.diagnostic.join(", ") || "(none)"}
+
+### Active Overrides
+${overrides}\n`
+      : `\n## 本章控制输入(由 Planner/Composer 编译)
+${chapterIntent}
+
+### 已选上下文
+${selectedContext || "- none"}
+
+### 规则栈
+- 硬护栏:${ruleStack.sections.hard.join("、") || "(无)"}
+- 软约束:${ruleStack.sections.soft.join("、") || "(无)"}
+- 诊断规则:${ruleStack.sections.diagnostic.join("、") || "(无)"}
+
+### 当前覆盖
+${overrides}\n`;
+  }
+
   private extractBalancedJson(text: string): string | null {
   private extractBalancedJson(text: string): string | null {
     const start = text.indexOf("{");
     const start = text.indexOf("{");
     if (start === -1) return null;
     if (start === -1) return null;

+ 1 - 1
packages/core/src/agents/en-prompt-sections.ts

@@ -29,7 +29,7 @@ export function buildEnglishCoreRules(_book: BookConfig): string {
 15. **No reset buttons**: The world must change permanently in response to major events.
 15. **No reset buttons**: The world must change permanently in response to major events.
 
 
 ### Reader Psychology
 ### Reader Psychology
-16. **Promise and payoff**: Every planted hook should eventually earn a payoff, reframing, or explicit defer. Every mystery needs an answer path, not aimless drift.
+16. **Promise and payoff**: Every planted hook must be resolved. Every mystery must have an answer.
 17. **Escalation**: Each conflict should feel higher-stakes than the last—either externally or emotionally.
 17. **Escalation**: Each conflict should feel higher-stakes than the last—either externally or emotionally.
 18. **Reader proxy**: One character should react with surprise/excitement/fear when remarkable things happen, giving readers permission to feel the same.
 18. **Reader proxy**: One character should react with surprise/excitement/fear when remarkable things happen, giving readers permission to feel the same.
 19. **Pacing breathing room**: After a high-intensity sequence, give 0.5-1 chapter of lower intensity before the next escalation.`;
 19. **Pacing breathing room**: After a high-intensity sequence, give 0.5-1 chapter of lower intensity before the next escalation.`;

+ 9 - 9
packages/core/src/agents/planner.ts

@@ -338,8 +338,8 @@ export class PlannerAgent extends BaseAgent {
     }
     }
 
 
     return this.isChineseLanguage(language)
     return this.isChineseLanguage(language)
-      ? "不要继续停留在卷纲 fallback 的保守走法里,优先把本章推向新的弧线节点或地点变化。"
-      : "Do not keep drifting inside the outline fallback. Prefer a fresh arc beat or location change this chapter.";
+      ? "不要继续依赖卷纲的 fallback 指令,必须把本章推进到新的弧线节点或地点变化。"
+      : "Do not keep leaning on the outline fallback. Force this chapter toward a fresh arc beat or location change.";
   }
   }
 
 
   private buildSceneDirective(
   private buildSceneDirective(
@@ -352,8 +352,8 @@ export class PlannerAgent extends BaseAgent {
     const repeatedType = cadence.scenePressure.repeatedType;
     const repeatedType = cadence.scenePressure.repeatedType;
 
 
     return this.isChineseLanguage(language)
     return this.isChineseLanguage(language)
-      ? `最近章节连续停留在“${repeatedType}”,本章最好更换场景容器、地点或行动方式,避免继续沿同一种节拍平移。`
-      : `Recent chapters are lingering in repeated ${repeatedType} beats. Change the scene container, location, or action pattern if possible instead of sliding forward on the same beat.`;
+      ? `最近章节连续停留在“${repeatedType}”,本章必须更换场景容器、地点或行动方式。`
+      : `Recent chapters are stuck in repeated ${repeatedType} beats. Change the scene container, location, or action pattern this chapter.`;
   }
   }
 
 
   private buildMoodDirective(
   private buildMoodDirective(
@@ -366,8 +366,8 @@ export class PlannerAgent extends BaseAgent {
     const moods = cadence.moodPressure.recentMoods;
     const moods = cadence.moodPressure.recentMoods;
 
 
     return this.isChineseLanguage(language)
     return this.isChineseLanguage(language)
-      ? `最近${moods.length}章情绪持续高压(${moods.slice(0, 3).join("、")}),建议本章降调——安排日常/喘息/温情/幽默场景,让读者呼吸。`
-      : `The last ${moods.length} chapters have been relentlessly tense (${moods.slice(0, 3).join(", ")}). Consider downshifting this chapter with warmth, humor, or breathing room.`;
+      ? `最近${moods.length}章情绪持续高压(${moods.slice(0, 3).join("、")}),本章必须降调——安排日常/喘息/温情/幽默场景,让读者呼吸。`
+      : `The last ${moods.length} chapters have been relentlessly tense (${moods.slice(0, 3).join(", ")}). This chapter must downshift — write a quieter scene with warmth, humor, or breathing room.`;
   }
   }
 
 
   private buildTitleDirective(
   private buildTitleDirective(
@@ -380,8 +380,8 @@ export class PlannerAgent extends BaseAgent {
     const repeatedToken = cadence.titlePressure.repeatedToken;
     const repeatedToken = cadence.titlePressure.repeatedToken;
 
 
     return this.isChineseLanguage(language)
     return this.isChineseLanguage(language)
-      ? `标题尽量不要再围绕“${repeatedToken}”重复命名,换一个新的意象或动作焦点。`
-      : `Try not to return to another ${repeatedToken}-centric title. Pick a new image or action focus for this chapter title.`;
+      ? `标题不要再围绕“${repeatedToken}”重复命名,换一个新的意象或动作焦点。`
+      : `Avoid another ${repeatedToken}-centric title. Pick a new image or action focus for this chapter title.`;
   }
   }
 
 
   private extractSection(content: string, headings: ReadonlyArray<string>): string | undefined {
   private extractSection(content: string, headings: ReadonlyArray<string>): string | undefined {
@@ -709,7 +709,7 @@ export class PlannerAgent extends BaseAgent {
       "## Style Emphasis",
       "## Style Emphasis",
       styleEmphasis,
       styleEmphasis,
       "",
       "",
-      "## Creative Pressure",
+      "## Structured Directives",
       directives,
       directives,
       "",
       "",
       "## Hook Agenda",
       "## Hook Agenda",

+ 29 - 8
packages/core/src/agents/reviser.ts

@@ -13,7 +13,6 @@ import {
   buildGovernedHookWorkingSet,
   buildGovernedHookWorkingSet,
   mergeTableMarkdownByKey,
   mergeTableMarkdownByKey,
 } from "../utils/governed-working-set.js";
 } from "../utils/governed-working-set.js";
-import { buildProcessBrief } from "../utils/compiled-control-brief.js";
 import { applySpotFixPatches, parseSpotFixPatches } from "../utils/spot-fix-patches.js";
 import { applySpotFixPatches, parseSpotFixPatches } from "../utils/spot-fix-patches.js";
 import { readFile } from "node:fs/promises";
 import { readFile } from "node:fs/promises";
 import { join } from "node:path";
 import { join } from "node:path";
@@ -226,13 +225,7 @@ ${outputFormat}`;
       ? `\n## 同人正典参照(修稿专用)\n本书为同人作品。修改时参照正典角色档案和世界规则,不可违反正典事实。角色对话必须保留原作语癖。\n${fanficCanon}\n`
       ? `\n## 同人正典参照(修稿专用)\n本书为同人作品。修改时参照正典角色档案和世界规则,不可违反正典事实。角色对话必须保留原作语癖。\n${fanficCanon}\n`
       : "";
       : "";
     const reducedControlBlock = options?.chapterIntent && options.contextPackage && options.ruleStack
     const reducedControlBlock = options?.chapterIntent && options.contextPackage && options.ruleStack
-      ? `\n${buildProcessBrief({
-          kind: "revision",
-          chapterIntent: options.chapterIntent,
-          contextPackage: options.contextPackage,
-          ruleStack: options.ruleStack,
-          language: resolvedLanguage,
-        })}\n`
+      ? this.buildReducedControlBlock(options.chapterIntent, options.contextPackage, options.ruleStack)
       : "";
       : "";
     const lengthGuidanceBlock = options?.lengthSpec
     const lengthGuidanceBlock = options?.lengthSpec
       ? `\n## 字数护栏\n目标字数:${options.lengthSpec.target}\n允许区间:${options.lengthSpec.softMin}-${options.lengthSpec.softMax}\n极限区间:${options.lengthSpec.hardMin}-${options.lengthSpec.hardMax}\n如果修正后超出允许区间,请优先压缩冗余解释、重复动作和弱信息句,不得新增支线或删掉核心事实。\n`
       ? `\n## 字数护栏\n目标字数:${options.lengthSpec.target}\n允许区间:${options.lengthSpec.softMin}-${options.lengthSpec.softMax}\n极限区间:${options.lengthSpec.hardMin}-${options.lengthSpec.hardMax}\n如果修正后超出允许区间,请优先压缩冗余解释、重复动作和弱信息句,不得新增支线或删掉核心事实。\n`
@@ -335,4 +328,32 @@ ${chapterContent}`;
     }
     }
   }
   }
 
 
+  private buildReducedControlBlock(
+    chapterIntent: string,
+    contextPackage: ContextPackage,
+    ruleStack: RuleStack,
+  ): string {
+    const selectedContext = contextPackage.selectedContext
+      .map((entry) => `- ${entry.source}: ${entry.reason}${entry.excerpt ? ` | ${entry.excerpt}` : ""}`)
+      .join("\n");
+    const overrides = ruleStack.activeOverrides.length > 0
+      ? ruleStack.activeOverrides
+        .map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
+        .join("\n")
+      : "- none";
+
+    return `\n## 本章控制输入(由 Planner/Composer 编译)
+${chapterIntent}
+
+### 已选上下文
+${selectedContext || "- none"}
+
+### 规则栈
+- 硬护栏:${ruleStack.sections.hard.join("、") || "(无)"}
+- 软约束:${ruleStack.sections.soft.join("、") || "(无)"}
+- 诊断规则:${ruleStack.sections.diagnostic.join("、") || "(无)"}
+
+### 当前覆盖
+${overrides}\n`;
+  }
 }
 }

+ 13 - 13
packages/core/src/agents/writer-prompts.ts

@@ -104,12 +104,12 @@ function buildGovernedInputContract(language: "zh" | "en", governed: boolean): s
 - The outline is the default plan, not unconditional global supremacy.
 - The outline is the default plan, not unconditional global supremacy.
 - When the runtime rule stack records an active L4 -> L3 override, follow the current task over local planning.
 - When the runtime rule stack records an active L4 -> L3 override, follow the current task over local planning.
 - Keep hard guardrails compact: canon, continuity facts, and explicit prohibitions still win.
 - Keep hard guardrails compact: canon, continuity facts, and explicit prohibitions still win.
-- If an English Variance Brief is provided, use it as a variance cue: avoid the listed phrase/opening/ending patterns and satisfy the scene obligation without sounding mechanical.
+- If an English Variance Brief is provided, obey it: avoid the listed phrase/opening/ending patterns and satisfy the scene obligation.
 - If Hook Debt Briefs are provided, treat them as the active memory of what the reader is still owed: preserve the original promise and change the on-page situation.
 - If Hook Debt Briefs are provided, treat them as the active memory of what the reader is still owed: preserve the original promise and change the on-page situation.
-- If the explicit hook agenda includes a pressure map, treat the requested move as the preferred narrative response for each target: full-payoff means concrete payoff, partial-payoff means a meaningful intermediate reveal, advance/refresh mean material movement, and quiet-hold means keep the promise visible without cashing it out early.
-- When the explicit hook agenda names an eligible resolve target, try to land a concrete payoff beat instead of merely mentioning the old thread.
-- When stale debt is present, be cautious about opening sibling hooks; relieve pressure from old promises before minting fresh debt unless the scene earns a new thread.
-- When a hook brief says to suppress sibling hooks, avoid faking progress by opening a parallel hook of the same family.
+- If the explicit hook agenda includes a pressure map, follow the requested move for each target: full-payoff means concrete payoff, partial-payoff means a meaningful intermediate reveal, advance/refresh mean material movement, and quiet-hold means keep the promise visible without cashing it out early.
+- When the explicit hook agenda names an eligible resolve target, land a concrete payoff beat instead of merely mentioning the old thread.
+- When stale debt is present, do not open sibling hooks casually; clear pressure from old promises before minting fresh debt.
+- When a hook brief says to suppress sibling hooks, do not fake progress by opening a parallel hook of the same family.
 - In multi-character scenes, include at least one resistance-bearing exchange instead of reducing the beat to summary or explanation.`;
 - In multi-character scenes, include at least one resistance-bearing exchange instead of reducing the beat to summary or explanation.`;
   }
   }
 
 
@@ -119,12 +119,12 @@ function buildGovernedInputContract(language: "zh" | "en", governed: boolean): s
 - 卷纲是默认规划,不是全局最高规则。
 - 卷纲是默认规划,不是全局最高规则。
 - 当 runtime rule stack 明确记录了 L4 -> L3 的 active override 时,优先执行当前任务意图,再局部调整规划层。
 - 当 runtime rule stack 明确记录了 L4 -> L3 的 active override 时,优先执行当前任务意图,再局部调整规划层。
 - 真正不能突破的只有硬护栏:世界设定、连续性事实、显式禁令。
 - 真正不能突破的只有硬护栏:世界设定、连续性事实、显式禁令。
-- 如果提供了 English Variance Brief,把它当成变奏提示:主动避开其中列出的高频短语、重复开头和重复结尾模式,同时自然完成 scene obligation。
+- 如果提供了 English Variance Brief,必须主动避开其中列出的高频短语、重复开头和重复结尾模式,并完成 scene obligation。
 - 如果提供了 Hook Debt 简报,把它当成读者仍在等待兑现的承诺记忆:保留原始 promise,并让本章在页上发生真实变化。
 - 如果提供了 Hook Debt 简报,把它当成读者仍在等待兑现的承诺记忆:保留原始 promise,并让本章在页上发生真实变化。
-- 如果显式 hook agenda 里带有 pressure map,把其中动作当成优先叙事响应:full-payoff 就是具体兑现,partial-payoff 就是给出中间层级的揭示或缩圈,advance / refresh 就是有分量的推进,quiet-hold 就是让承诺继续可见但不要过早消耗。
-- 如果显式 hook agenda 里出现了可回收目标,优先写出具体兑现片段,不要只是重新提一句旧线索。
-- 如果存在 stale debt,先消化旧承诺的压力,再决定是否开新坑;同类 sibling hook 要谨慎新增,除非场景真的挣到了新的线程。
-- 如果某条 hook 简报明确要求 suppress sibling hooks,避免靠再开一个同类平行坑来假装推进。
+- 如果显式 hook agenda 里带有 pressure map,逐条执行其中要求的动作:full-payoff 就是具体兑现,partial-payoff 就是给出中间层级的揭示或缩圈,advance / refresh 就是有分量的推进,quiet-hold 就是让承诺继续可见但不要过早消耗。
+- 如果显式 hook agenda 里出现了可回收目标,本章必须写出具体兑现片段,不能只是重新提一句旧线索。
+- 如果存在 stale debt,先消化旧承诺的压力,再决定是否开新坑;同类 sibling hook 不得随手再开。
+- 如果某条 hook 简报明确要求 suppress sibling hooks,就不能用再开一个同类平行坑来假装推进。
 - 多角色场景里,至少给出一轮带阻力的直接交锋,不要把人物关系写成纯解释或纯总结。`;
 - 多角色场景里,至少给出一轮带阻力的直接交锋,不要把人物关系写成纯解释或纯总结。`;
 }
 }
 
 
@@ -153,7 +153,7 @@ function buildCoreRules(lengthSpec: LengthSpec): string {
 
 
 1. 以简体中文工作,句子长短交替,段落适合手机阅读(3-5行/段)
 1. 以简体中文工作,句子长短交替,段落适合手机阅读(3-5行/段)
 2. 目标字数:${lengthSpec.target}字,允许区间:${lengthSpec.softMin}-${lengthSpec.softMax}字
 2. 目标字数:${lengthSpec.target}字,允许区间:${lengthSpec.softMin}-${lengthSpec.softMax}字
-3. 伏笔前后呼应,不留悬空线;所有埋下的伏笔都应在后续得到兑现、转化或明确延后
+3. 伏笔前后呼应,不留悬空线;所有埋下的伏笔都必须在后续收回
 4. 只读必要上下文,不机械重复已有内容
 4. 只读必要上下文,不机械重复已有内容
 
 
 ## 人物塑造铁律
 ## 人物塑造铁律
@@ -545,7 +545,7 @@ ${resourceRow}| 待回收伏笔 | 用真实 hook_id 填写(无则写 none) |
 ${preWriteTable}
 ${preWriteTable}
 
 
 === CHAPTER_TITLE ===
 === CHAPTER_TITLE ===
-(章节标题,不含"第X章"。标题应与已有章节标题区分开,避免重复使用相同或相似的标题;若提供了 recent title history 或高频标题词,优先避开重复词根和高频意象)
+(章节标题,不含"第X章"。标题必须与已有章节标题不同,不要重复使用相同或相似的标题;若提供了 recent title history 或高频标题词,必须主动避开重复词根和高频意象)
 
 
 === CHAPTER_CONTENT ===
 === CHAPTER_CONTENT ===
 (正文内容,目标${lengthSpec.target}字,允许区间${lengthSpec.softMin}-${lengthSpec.softMax}字)
 (正文内容,目标${lengthSpec.target}字,允许区间${lengthSpec.softMin}-${lengthSpec.softMax}字)
@@ -598,7 +598,7 @@ ${resourceRow}| 待回收伏笔 | 用真实 hook_id 填写(无则写 none) |
 ${preWriteTable}
 ${preWriteTable}
 
 
 === CHAPTER_TITLE ===
 === CHAPTER_TITLE ===
-(章节标题,不含"第X章"。标题应与已有章节标题区分开,避免重复使用相同或相似的标题;若提供了 recent title history 或高频标题词,优先避开重复词根和高频意象)
+(章节标题,不含"第X章"。标题必须与已有章节标题不同,不要重复使用相同或相似的标题;若提供了 recent title history 或高频标题词,必须主动避开重复词根和高频意象)
 
 
 === CHAPTER_CONTENT ===
 === CHAPTER_CONTENT ===
 (正文内容,目标${lengthSpec.target}字,允许区间${lengthSpec.softMin}-${lengthSpec.softMax}字)
 (正文内容,目标${lengthSpec.target}字,允许区间${lengthSpec.softMin}-${lengthSpec.softMax}字)

+ 127 - 24
packages/core/src/agents/writer.ts

@@ -34,7 +34,6 @@ import type { RuntimeStateSnapshot } from "../state/state-reducer.js";
 import { parsePendingHooksMarkdown } from "../utils/memory-retrieval.js";
 import { parsePendingHooksMarkdown } from "../utils/memory-retrieval.js";
 import { analyzeHookHealth } from "../utils/hook-health.js";
 import { analyzeHookHealth } from "../utils/hook-health.js";
 import { buildEnglishVarianceBrief } from "../utils/long-span-fatigue.js";
 import { buildEnglishVarianceBrief } from "../utils/long-span-fatigue.js";
-import { buildSettlementFocus, buildStoryBrief } from "../utils/compiled-control-brief.js";
 import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
 import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
 import { join } from "node:path";
 import { join } from "node:path";
 
 
@@ -196,6 +195,7 @@ export class WriterAgent extends BaseAgent {
           lengthSpec: resolvedLengthSpec,
           lengthSpec: resolvedLengthSpec,
           language: book.language ?? genreProfile.language,
           language: book.language ?? genreProfile.language,
           varianceBrief: englishVarianceBrief?.text,
           varianceBrief: englishVarianceBrief?.text,
+          selectedEvidenceBlock: this.joinGovernedEvidenceBlocks(governedMemoryBlocks),
         })
         })
       : (() => {
       : (() => {
           // Smart context filtering: inject only relevant parts of truth files
           // Smart context filtering: inject only relevant parts of truth files
@@ -810,23 +810,64 @@ ${lengthRequirementBlock}
     readonly lengthSpec: LengthSpec;
     readonly lengthSpec: LengthSpec;
     readonly language?: "zh" | "en";
     readonly language?: "zh" | "en";
     readonly varianceBrief?: string;
     readonly varianceBrief?: string;
+    readonly selectedEvidenceBlock?: string;
   }): string {
   }): string {
+    const contextSections = params.contextPackage.selectedContext
+      .map((entry) => [
+        `### ${entry.source}`,
+        `- reason: ${entry.reason}`,
+        entry.excerpt ? `- excerpt: ${entry.excerpt}` : "",
+      ].filter(Boolean).join("\n"))
+      .join("\n\n");
+
+    const overrideLines = params.ruleStack.activeOverrides.length > 0
+      ? params.ruleStack.activeOverrides
+        .map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
+        .join("\n")
+      : "- none";
+
+    const diagnosticLines = params.ruleStack.sections.diagnostic.length > 0
+      ? params.ruleStack.sections.diagnostic.join(", ")
+      : "none";
+
+    const traceNotes = params.trace && params.trace.notes.length > 0
+      ? params.trace.notes.map((note) => `- ${note}`).join("\n")
+      : "- none";
     const lengthRequirementBlock = this.buildLengthRequirementBlock(params.lengthSpec, params.language ?? "zh");
     const lengthRequirementBlock = this.buildLengthRequirementBlock(params.lengthSpec, params.language ?? "zh");
     const varianceBlock = params.varianceBrief
     const varianceBlock = params.varianceBrief
       ? `\n${params.varianceBrief}\n`
       ? `\n${params.varianceBrief}\n`
       : "";
       : "";
-    const storyBrief = this.buildGovernedStoryBrief({
-      chapterIntent: params.chapterIntent,
-      contextPackage: params.contextPackage,
-      ruleStack: params.ruleStack,
-      trace: params.trace,
-      language: params.language ?? "zh",
-    });
+    const selectedEvidenceBlock = params.selectedEvidenceBlock
+      ? `\n${params.selectedEvidenceBlock}\n`
+      : "";
+    const explicitHookAgenda = this.extractMarkdownSection(params.chapterIntent, "## Hook Agenda");
+    const hookAgendaBlock = explicitHookAgenda
+      ? params.language === "en"
+        ? `\n## Explicit Hook Agenda\n${explicitHookAgenda}\n`
+        : `\n## 显式 Hook Agenda\n${explicitHookAgenda}\n`
+      : "";
 
 
     if (params.language === "en") {
     if (params.language === "en") {
       return `Write chapter ${params.chapterNumber}.
       return `Write chapter ${params.chapterNumber}.
 
 
-${storyBrief}
+## Chapter Intent
+${params.chapterIntent}
+
+## Selected Context
+${contextSections || "(none)"}
+${selectedEvidenceBlock}
+${hookAgendaBlock}
+
+## Rule Stack
+- Hard: ${params.ruleStack.sections.hard.join(", ") || "(none)"}
+- Soft: ${params.ruleStack.sections.soft.join(", ") || "(none)"}
+- Diagnostic: ${diagnosticLines}
+
+## Active Overrides
+${overrideLines}
+
+## Trace Notes
+${traceNotes}
 
 
 ${varianceBlock}
 ${varianceBlock}
 ${lengthRequirementBlock}
 ${lengthRequirementBlock}
@@ -836,7 +877,24 @@ ${lengthRequirementBlock}
 
 
     return `请续写第${params.chapterNumber}章。
     return `请续写第${params.chapterNumber}章。
 
 
-${storyBrief}
+## 本章意图
+${params.chapterIntent}
+
+## 已选上下文
+${contextSections || "(无)"}
+${selectedEvidenceBlock}
+${hookAgendaBlock}
+
+## 规则栈
+- 硬护栏:${params.ruleStack.sections.hard.join("、") || "(无)"}
+- 软约束:${params.ruleStack.sections.soft.join("、") || "(无)"}
+- 诊断规则:${diagnosticLines}
+
+## 当前覆盖
+${overrideLines}
+
+## 追踪说明
+${traceNotes}
 
 
 ${varianceBlock}
 ${varianceBlock}
 ${lengthRequirementBlock}
 ${lengthRequirementBlock}
@@ -864,14 +922,27 @@ ${lengthRequirementBlock}
     return joined || undefined;
     return joined || undefined;
   }
   }
 
 
-  private buildGovernedStoryBrief(params: {
-    readonly chapterIntent: string;
-    readonly contextPackage: ContextPackage;
-    readonly ruleStack: RuleStack;
-    readonly trace?: ChapterTrace;
-    readonly language: "zh" | "en";
-  }): string {
-    return buildStoryBrief(params);
+  private extractMarkdownSection(content: string, heading: string): string | undefined {
+    const lines = content.split("\n");
+    let buffer: string[] | null = null;
+
+    for (const line of lines) {
+      if (line.trim() === heading) {
+        buffer = [];
+        continue;
+      }
+
+      if (buffer && line.startsWith("## ") && line.trim() !== heading) {
+        break;
+      }
+
+      if (buffer) {
+        buffer.push(line);
+      }
+    }
+
+    const section = buffer?.join("\n").trim();
+    return section && section.length > 0 ? section : undefined;
   }
   }
 
 
   private buildSettlerGovernedControlBlock(
   private buildSettlerGovernedControlBlock(
@@ -880,12 +951,44 @@ ${lengthRequirementBlock}
     ruleStack: RuleStack,
     ruleStack: RuleStack,
     language: "zh" | "en",
     language: "zh" | "en",
   ): string {
   ): string {
-    return buildSettlementFocus({
-      chapterIntent,
-      contextPackage,
-      ruleStack,
-      language,
-    });
+    const selectedContext = contextPackage.selectedContext
+      .map((entry) => `- ${entry.source}: ${entry.reason}${entry.excerpt ? ` | ${entry.excerpt}` : ""}`)
+      .join("\n");
+    const overrides = ruleStack.activeOverrides.length > 0
+      ? ruleStack.activeOverrides
+        .map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
+        .join("\n")
+      : "- none";
+
+    if (language === "en") {
+      return `\n## Chapter Control Inputs
+${chapterIntent}
+
+### Selected Context
+${selectedContext || "- none"}
+
+### Rule Stack
+- Hard guardrails: ${ruleStack.sections.hard.join(", ") || "(none)"}
+- Soft constraints: ${ruleStack.sections.soft.join(", ") || "(none)"}
+- Diagnostic rules: ${ruleStack.sections.diagnostic.join(", ") || "(none)"}
+
+### Active Overrides
+${overrides}\n`;
+    }
+
+    return `\n## 本章控制输入
+${chapterIntent}
+
+### 已选上下文
+${selectedContext || "- none"}
+
+### 规则栈
+- 硬护栏:${ruleStack.sections.hard.join("、") || "(无)"}
+- 软约束:${ruleStack.sections.soft.join("、") || "(无)"}
+- 诊断规则:${ruleStack.sections.diagnostic.join("、") || "(无)"}
+
+### 当前覆盖
+${overrides}\n`;
   }
   }
 
 
   private buildLengthRequirementBlock(lengthSpec: LengthSpec, language: "zh" | "en"): string {
   private buildLengthRequirementBlock(lengthSpec: LengthSpec, language: "zh" | "en"): string {

+ 0 - 360
packages/core/src/utils/compiled-control-brief.ts

@@ -1,360 +0,0 @@
-import type { ChapterTrace, ContextPackage, RuleStack } from "../models/input-governance.js";
-
-type PromptLanguage = "zh" | "en";
-type ProcessBriefKind = "analysis" | "review" | "revision";
-
-interface BriefInput {
-  readonly chapterIntent: string;
-  readonly contextPackage: ContextPackage;
-  readonly ruleStack: RuleStack;
-  readonly language: PromptLanguage;
-  readonly trace?: ChapterTrace;
-}
-
-interface CompiledControlSummary {
-  readonly goal?: string;
-  readonly mustKeep: string[];
-  readonly mustAvoid: string[];
-  readonly styleEmphasis: string[];
-  readonly creativePressure: string[];
-  readonly hookFocus: string[];
-  readonly stateAnchors: string[];
-  readonly storyGuardrails: string[];
-  readonly outlineAnchors: string[];
-  readonly canonGuardrails: string[];
-  readonly titleHistory?: string;
-  readonly moodTrail?: string;
-  readonly overrideLines: string[];
-  readonly traceNotes: string[];
-}
-
-export function buildStoryBrief(params: BriefInput): string {
-  const summary = compileControlSummary(params);
-  const continuityAnchors = uniqueContextLines([
-    ...summary.stateAnchors,
-    ...summary.storyGuardrails,
-    ...summary.outlineAnchors,
-  ]);
-
-  if (params.language === "en") {
-    return [
-      "## Story Brief",
-      summary.goal ? `Goal: ${summary.goal}` : "",
-      renderCompactList("Keep continuity on", summary.mustKeep),
-      renderCompactList("Avoid this chapter", summary.mustAvoid),
-      renderCompactList("Style emphasis", summary.styleEmphasis),
-      renderCompactList("Creative pressure", summary.creativePressure),
-      renderCompactList("Hook focus", summary.hookFocus),
-      renderCompactList("Continuity anchors", continuityAnchors),
-      renderCompactList("Canon guardrails", summary.canonGuardrails),
-      summary.titleHistory ? `Recent title history: ${summary.titleHistory}` : "",
-      summary.moodTrail ? `Recent mood/type trail: ${summary.moodTrail}` : "",
-      renderCompactList("Local overrides", summary.overrideLines),
-      renderCompactList("Trace notes", summary.traceNotes),
-    ].filter(Boolean).join("\n\n");
-  }
-
-  return [
-    "## Story Brief",
-    summary.goal ? `目标:${summary.goal}` : "",
-    renderCompactList("本章必须守住", summary.mustKeep),
-    renderCompactList("本章避免", summary.mustAvoid),
-    renderCompactList("文风强调", summary.styleEmphasis),
-    renderCompactList("创作压力", summary.creativePressure),
-    renderCompactList("伏笔焦点", summary.hookFocus),
-    renderCompactList("连续性锚点", continuityAnchors),
-    renderCompactList("正典护栏", summary.canonGuardrails),
-    summary.titleHistory ? `近期标题历史:${summary.titleHistory}` : "",
-    summary.moodTrail ? `近期情绪/章节类型轨迹:${summary.moodTrail}` : "",
-    renderCompactList("局部覆盖", summary.overrideLines),
-    renderCompactList("追踪备注", summary.traceNotes),
-  ].filter(Boolean).join("\n\n");
-}
-
-export function buildSettlementFocus(params: Omit<BriefInput, "trace">): string {
-  const summary = compileControlSummary(params);
-  const continuityAnchors = uniqueContextLines([
-    ...summary.stateAnchors,
-    ...summary.storyGuardrails,
-    ...summary.outlineAnchors,
-    ...summary.canonGuardrails,
-  ]);
-
-  if (params.language === "en") {
-    return `\n## Settlement Focus
-${summary.goal ? `Goal: ${summary.goal}\n` : ""}${renderCompactList("Keep synced with", summary.mustKeep)}${renderCompactList("Hook settlement cues", summary.hookFocus)}${renderCompactList("Continuity anchors", continuityAnchors)}${renderCompactList("Local overrides", summary.overrideLines)}\n`;
-  }
-
-  return `\n## 结算焦点
-${summary.goal ? `目标:${summary.goal}\n` : ""}${renderCompactList("结算时守住", summary.mustKeep)}${renderCompactList("伏笔结算提示", summary.hookFocus)}${renderCompactList("连续性锚点", continuityAnchors)}${renderCompactList("局部覆盖", summary.overrideLines)}\n`;
-}
-
-export function buildProcessBrief(
-  params: BriefInput & { readonly kind: ProcessBriefKind },
-): string {
-  const summary = compileControlSummary(params);
-  const heading = params.kind === "analysis"
-    ? "## Analysis Brief"
-    : params.kind === "review"
-      ? "## Review Brief"
-      : "## Revision Brief";
-
-  if (params.language === "en") {
-    return [
-      heading,
-      summary.goal ? `Goal: ${summary.goal}` : "",
-      renderCompactList(
-        params.kind === "analysis"
-          ? "Keep analysis aligned with"
-          : params.kind === "review"
-            ? "Review against"
-            : "Preserve while revising",
-        summary.mustKeep,
-      ),
-      renderCompactList(
-        params.kind === "analysis"
-          ? "Avoid misreading"
-          : params.kind === "review"
-            ? "Flag if chapter drifts into"
-            : "Avoid while revising",
-        summary.mustAvoid,
-      ),
-      renderCompactList(
-        params.kind === "analysis"
-          ? "Update hook state around"
-          : params.kind === "review"
-            ? "Hook focus"
-            : "Hook details to preserve",
-        summary.hookFocus,
-      ),
-      renderCompactList("Current-state anchors", summary.stateAnchors),
-      renderCompactList("Outline anchor", summary.outlineAnchors),
-      renderCompactList("World rules", summary.storyGuardrails),
-      renderCompactList("Canon guardrails", summary.canonGuardrails),
-      renderCompactList("Local overrides", summary.overrideLines),
-      params.kind === "analysis" ? renderCompactList("Trace notes", summary.traceNotes) : "",
-    ].filter(Boolean).join("\n\n");
-  }
-
-  return [
-    heading,
-    summary.goal ? `目标:${summary.goal}` : "",
-    renderCompactList(
-      params.kind === "analysis"
-        ? "更新时守住"
-        : params.kind === "review"
-          ? "重点核对"
-          : "修订时守住",
-      summary.mustKeep,
-    ),
-    renderCompactList(
-      params.kind === "analysis"
-        ? "避免误读"
-        : params.kind === "review"
-          ? "若出现则重点标记"
-          : "修订时避免",
-      summary.mustAvoid,
-    ),
-    renderCompactList(
-      params.kind === "analysis"
-        ? "更新伏笔时参考"
-        : params.kind === "review"
-          ? "伏笔焦点"
-          : "伏笔细节保留",
-      summary.hookFocus,
-    ),
-    renderCompactList("当前状态锚点", summary.stateAnchors),
-    renderCompactList("卷纲锚点", summary.outlineAnchors),
-    renderCompactList("世界规则", summary.storyGuardrails),
-    renderCompactList("正典护栏", summary.canonGuardrails),
-    renderCompactList("局部覆盖", summary.overrideLines),
-    params.kind === "analysis" ? renderCompactList("追踪备注", summary.traceNotes) : "",
-  ].filter(Boolean).join("\n\n");
-}
-
-function compileControlSummary(params: BriefInput): CompiledControlSummary {
-  return {
-    goal: readIntentScalar(params.chapterIntent, "## Goal"),
-    mustKeep: readIntentList(params.chapterIntent, "## Must Keep"),
-    mustAvoid: readIntentList(params.chapterIntent, "## Must Avoid"),
-    styleEmphasis: readIntentList(params.chapterIntent, "## Style Emphasis"),
-    creativePressure: readIntentList(params.chapterIntent, "## Creative Pressure"),
-    hookFocus: buildHookFocusLines(params.chapterIntent, params.contextPackage, params.language),
-    stateAnchors: collectContextExcerpts(params.contextPackage, (entry) =>
-      entry.source.startsWith("story/current_state.md"),
-    ),
-    storyGuardrails: collectContextExcerpts(params.contextPackage, (entry) =>
-      entry.source === "story/story_bible.md",
-    ),
-    outlineAnchors: collectContextExcerpts(params.contextPackage, (entry) =>
-      entry.source === "story/volume_outline.md",
-    ),
-    canonGuardrails: collectContextExcerpts(params.contextPackage, (entry) =>
-      entry.source === "story/parent_canon.md" || entry.source === "story/fanfic_canon.md",
-    ),
-    titleHistory: findContextExcerpt(params.contextPackage, "story/chapter_summaries.md#recent_titles"),
-    moodTrail: findContextExcerpt(
-      params.contextPackage,
-      "story/chapter_summaries.md#recent_mood_type_trail",
-    ),
-    overrideLines: params.ruleStack.activeOverrides
-      .map((override) => `${override.reason} (${override.target})`),
-    traceNotes: params.trace?.notes.filter(Boolean) ?? [],
-  };
-}
-
-function readIntentScalar(chapterIntent: string, heading: string): string | undefined {
-  const section = extractMarkdownSection(chapterIntent, heading);
-  if (!section) {
-    return undefined;
-  }
-
-  return section
-    .split("\n")
-    .map((value) => value.trim())
-    .find((value) => value.length > 0 && !value.startsWith("-") && value.toLowerCase() !== "(not found)");
-}
-
-function readIntentList(chapterIntent: string, heading: string): string[] {
-  const section = extractMarkdownSection(chapterIntent, heading);
-  if (!section) {
-    return [];
-  }
-
-  return section
-    .split("\n")
-    .map((value) => value.trim())
-    .filter((value) => value.startsWith("- "))
-    .map((value) => value.slice(2).trim())
-    .filter((value) => value.length > 0 && value.toLowerCase() !== "none");
-}
-
-function extractMarkdownSection(content: string, heading: string): string | undefined {
-  const lines = content.split("\n");
-  let buffer: string[] | null = null;
-
-  for (const line of lines) {
-    if (line.trim() === heading) {
-      buffer = [];
-      continue;
-    }
-
-    if (buffer && line.startsWith("## ") && line.trim() !== heading) {
-      break;
-    }
-
-    if (buffer) {
-      buffer.push(line);
-    }
-  }
-
-  const section = buffer?.join("\n").trim();
-  return section && section.length > 0 ? section : undefined;
-}
-
-function buildHookFocusLines(
-  chapterIntent: string,
-  contextPackage: ContextPackage,
-  language: PromptLanguage,
-): string[] {
-  const hookDebtLines = collectContextExcerpts(
-    contextPackage,
-    (entry) => entry.source.startsWith("runtime/hook_debt#"),
-  );
-  const fallbackLines = [
-    ...formatAgendaLines(chapterIntent, "Must Advance", language === "en" ? "Must advance" : "必须推进"),
-    ...formatAgendaLines(chapterIntent, "Eligible Resolve", language === "en" ? "Eligible payoff" : "可兑现"),
-    ...formatAgendaLines(chapterIntent, "Stale Debt", language === "en" ? "Stale debt" : "旧债"),
-    ...formatAgendaLines(
-      chapterIntent,
-      "Avoid New Hook Families",
-      language === "en" ? "Avoid new hook families" : "避免新增同类 hook",
-    ),
-  ];
-
-  return uniqueContextLines([
-    ...(hookDebtLines.length > 0 ? hookDebtLines : []),
-    ...fallbackLines,
-  ]).slice(0, 6);
-}
-
-function formatAgendaLines(
-  chapterIntent: string,
-  subheading: string,
-  label: string,
-): string[] {
-  const hookAgenda = extractMarkdownSection(chapterIntent, "## Hook Agenda");
-  if (!hookAgenda) {
-    return [];
-  }
-
-  const lines = hookAgenda.split("\n");
-  const values: string[] = [];
-  let capture = false;
-
-  for (const rawLine of lines) {
-    const line = rawLine.trim();
-    if (line === `### ${subheading}`) {
-      capture = true;
-      continue;
-    }
-    if (capture && line.startsWith("### ")) {
-      break;
-    }
-    if (capture && line.startsWith("- ")) {
-      const value = line.slice(2).trim();
-      if (value && value.toLowerCase() !== "none") {
-        values.push(`${label}: ${value}`);
-      }
-    }
-  }
-
-  return values;
-}
-
-function collectContextExcerpts(
-  contextPackage: ContextPackage,
-  predicate: (entry: ContextPackage["selectedContext"][number]) => boolean,
-): string[] {
-  return uniqueContextLines(
-    contextPackage.selectedContext
-      .filter(predicate)
-      .map((entry) => entry.excerpt ?? entry.reason)
-      .filter(Boolean),
-  );
-}
-
-function findContextExcerpt(
-  contextPackage: ContextPackage,
-  source: string,
-): string | undefined {
-  return contextPackage.selectedContext.find((entry) => entry.source === source)?.excerpt;
-}
-
-function renderCompactList(heading: string, items: ReadonlyArray<string>): string {
-  if (items.length === 0) {
-    return "";
-  }
-
-  return `${heading}:\n${items.map((item) => `- ${item}`).join("\n")}`;
-}
-
-function uniqueContextLines(items: ReadonlyArray<string>): string[] {
-  const seen = new Set<string>();
-  const result: string[] = [];
-
-  for (const item of items) {
-    const trimmed = item.trim();
-    if (!trimmed) {
-      continue;
-    }
-    const normalized = trimmed.toLowerCase();
-    if (seen.has(normalized)) {
-      continue;
-    }
-    seen.add(normalized);
-    result.push(trimmed);
-  }
-
-  return result;
-}