Forráskód Böngészése

refactor(prompt): share compact governed process briefs

Ma 7 hónapja
szülő
commit
eb288b31f8

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

@@ -561,7 +561,8 @@ 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("## Chapter Control Inputs (compiled by Planner/Composer)");
+      expect(userPrompt).toContain("## Analysis Brief");
+      expect(userPrompt).not.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");

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

@@ -281,6 +281,8 @@ 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 |");

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

@@ -453,10 +453,14 @@ 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("story/story_bible.md");
-      expect(userPrompt).toContain("story/volume_outline.md");
+      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).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.");

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

@@ -12,6 +12,7 @@ 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";
 
 
@@ -95,7 +96,13 @@ 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
-      ? this.buildReducedControlBlock(input.chapterIntent, input.contextPackage, input.ruleStack, resolvedLanguage)
+      ? `\n${buildProcessBrief({
+          kind: "analysis",
+          chapterIntent: input.chapterIntent,
+          contextPackage: input.contextPackage,
+          ruleStack: input.ruleStack,
+          language: resolvedLanguage,
+        })}\n`
       : "";
       : "";
 
 
     const systemPrompt = this.buildSystemPrompt(
     const systemPrompt = this.buildSystemPrompt(
@@ -461,50 +468,6 @@ ${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)

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

@@ -8,6 +8,7 @@ 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 {
@@ -498,7 +499,13 @@ ${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
-      ? this.buildReducedControlBlock(options.chapterIntent, options.contextPackage, options.ruleStack, resolvedLanguage)
+      ? `\n${buildProcessBrief({
+          kind: "review",
+          chapterIntent: options.chapterIntent,
+          contextPackage: options.contextPackage,
+          ruleStack: options.ruleStack,
+          language: resolvedLanguage,
+        })}\n`
       : "";
       : "";
     const styleGuideBlock = reducedControlBlock.length === 0
     const styleGuideBlock = reducedControlBlock.length === 0
       ? isEnglish
       ? isEnglish
@@ -618,50 +625,6 @@ ${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;

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

@@ -13,6 +13,7 @@ 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";
@@ -225,7 +226,13 @@ ${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
-      ? this.buildReducedControlBlock(options.chapterIntent, options.contextPackage, options.ruleStack)
+      ? `\n${buildProcessBrief({
+          kind: "revision",
+          chapterIntent: options.chapterIntent,
+          contextPackage: options.contextPackage,
+          ruleStack: options.ruleStack,
+          language: resolvedLanguage,
+        })}\n`
       : "";
       : "";
     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`
@@ -328,32 +335,4 @@ ${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`;
-  }
 }
 }

+ 7 - 235
packages/core/src/agents/writer.ts

@@ -34,6 +34,7 @@ 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";
 
 
@@ -870,87 +871,7 @@ ${lengthRequirementBlock}
     readonly trace?: ChapterTrace;
     readonly trace?: ChapterTrace;
     readonly language: "zh" | "en";
     readonly language: "zh" | "en";
   }): string {
   }): string {
-    const goal = this.readIntentScalar(params.chapterIntent, "## Goal");
-    const mustKeep = this.readIntentList(params.chapterIntent, "## Must Keep");
-    const mustAvoid = this.readIntentList(params.chapterIntent, "## Must Avoid");
-    const styleEmphasis = this.readIntentList(params.chapterIntent, "## Style Emphasis");
-    const creativePressure = this.readIntentList(params.chapterIntent, "## Creative Pressure");
-    const hookFocus = this.buildHookFocusLines(params.chapterIntent, params.contextPackage, params.language);
-    const continuityAnchors = this.collectContextExcerpts(params.contextPackage, (entry) =>
-      entry.source.startsWith("story/current_state.md")
-      || entry.source === "story/story_bible.md",
-    );
-    const canonGuardrails = this.collectContextExcerpts(params.contextPackage, (entry) =>
-      entry.source === "story/parent_canon.md" || entry.source === "story/fanfic_canon.md",
-    );
-    const titleHistory = this.findContextExcerpt(
-      params.contextPackage,
-      "story/chapter_summaries.md#recent_titles",
-    );
-    const moodTrail = this.findContextExcerpt(
-      params.contextPackage,
-      "story/chapter_summaries.md#recent_mood_type_trail",
-    );
-    const overrideLines = params.ruleStack.activeOverrides
-      .map((override) => `${override.reason} (${override.target})`);
-    const traceNotes = params.trace?.notes.filter(Boolean) ?? [];
-
-    if (params.language === "en") {
-      return [
-        "## Story Brief",
-        goal ? `Goal: ${goal}` : "",
-        this.renderCompactList("Keep continuity on", mustKeep),
-        this.renderCompactList("Avoid this chapter", mustAvoid),
-        this.renderCompactList("Style emphasis", styleEmphasis),
-        this.renderCompactList("Creative pressure", creativePressure),
-        this.renderCompactList("Hook focus", hookFocus),
-        this.renderCompactList("Continuity anchors", continuityAnchors),
-        this.renderCompactList("Canon guardrails", canonGuardrails),
-        titleHistory ? `Recent title history: ${titleHistory}` : "",
-        moodTrail ? `Recent mood/type trail: ${moodTrail}` : "",
-        this.renderCompactList("Local overrides", overrideLines),
-        this.renderCompactList("Trace notes", traceNotes),
-      ].filter(Boolean).join("\n\n");
-    }
-
-    return [
-      "## Story Brief",
-      goal ? `目标:${goal}` : "",
-      this.renderCompactList("本章必须守住", mustKeep),
-      this.renderCompactList("本章避免", mustAvoid),
-      this.renderCompactList("文风强调", styleEmphasis),
-      this.renderCompactList("创作压力", creativePressure),
-      this.renderCompactList("伏笔焦点", hookFocus),
-      this.renderCompactList("连续性锚点", continuityAnchors),
-      this.renderCompactList("正典护栏", canonGuardrails),
-      titleHistory ? `近期标题历史:${titleHistory}` : "",
-      moodTrail ? `近期情绪/章节类型轨迹:${moodTrail}` : "",
-      this.renderCompactList("局部覆盖", overrideLines),
-      this.renderCompactList("追踪备注", traceNotes),
-    ].filter(Boolean).join("\n\n");
-  }
-
-  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;
+    return buildStoryBrief(params);
   }
   }
 
 
   private buildSettlerGovernedControlBlock(
   private buildSettlerGovernedControlBlock(
@@ -959,161 +880,12 @@ ${lengthRequirementBlock}
     ruleStack: RuleStack,
     ruleStack: RuleStack,
     language: "zh" | "en",
     language: "zh" | "en",
   ): string {
   ): string {
-    const goal = this.readIntentScalar(chapterIntent, "## Goal");
-    const mustKeep = this.readIntentList(chapterIntent, "## Must Keep");
-    const hookFocus = this.buildHookFocusLines(chapterIntent, contextPackage, language);
-    const continuityAnchors = this.collectContextExcerpts(contextPackage, (entry) =>
-      entry.source.startsWith("story/current_state.md")
-      || entry.source === "story/story_bible.md"
-      || entry.source === "story/parent_canon.md"
-      || entry.source === "story/fanfic_canon.md",
-    );
-    const overrides = ruleStack.activeOverrides
-      .map((override) => `${override.reason} (${override.target})`);
-
-    if (language === "en") {
-      return `\n## Settlement Focus
-${goal ? `Goal: ${goal}\n` : ""}${this.renderCompactList("Keep synced with", mustKeep)}${this.renderCompactList("Hook settlement cues", hookFocus)}${this.renderCompactList("Continuity anchors", continuityAnchors)}${this.renderCompactList("Local overrides", overrides)}\n`;
-    }
-
-    return `\n## 结算焦点
-${goal ? `目标:${goal}\n` : ""}${this.renderCompactList("结算时守住", mustKeep)}${this.renderCompactList("伏笔结算提示", hookFocus)}${this.renderCompactList("连续性锚点", continuityAnchors)}${this.renderCompactList("局部覆盖", overrides)}\n`;
-  }
-
-  private readIntentScalar(chapterIntent: string, heading: string): string | undefined {
-    const section = this.extractMarkdownSection(chapterIntent, heading);
-    if (!section) {
-      return undefined;
-    }
-
-    return section
-      .split("\n")
-      .map((line) => line.trim())
-      .find((line) => line.length > 0 && !line.startsWith("-") && line.toLowerCase() !== "(not found)");
-  }
-
-  private readIntentList(chapterIntent: string, heading: string): string[] {
-    const section = this.extractMarkdownSection(chapterIntent, heading);
-    if (!section) {
-      return [];
-    }
-
-    return section
-      .split("\n")
-      .map((line) => line.trim())
-      .filter((line) => line.startsWith("- "))
-      .map((line) => line.slice(2).trim())
-      .filter((line) => line.length > 0 && line.toLowerCase() !== "none");
-  }
-
-  private buildHookFocusLines(
-    chapterIntent: string,
-    contextPackage: ContextPackage,
-    language: "zh" | "en",
-  ): string[] {
-    const hookDebtLines = this.collectContextExcerpts(
+    return buildSettlementFocus({
+      chapterIntent,
       contextPackage,
       contextPackage,
-      (entry) => entry.source.startsWith("runtime/hook_debt#"),
-    );
-    const fallbackLines = [
-      ...this.formatAgendaLines(chapterIntent, "Must Advance", language === "en" ? "Must advance" : "必须推进"),
-      ...this.formatAgendaLines(chapterIntent, "Eligible Resolve", language === "en" ? "Eligible payoff" : "可兑现"),
-      ...this.formatAgendaLines(chapterIntent, "Stale Debt", language === "en" ? "Stale debt" : "旧债"),
-      ...this.formatAgendaLines(
-        chapterIntent,
-        "Avoid New Hook Families",
-        language === "en" ? "Avoid new hook families" : "避免新增同类 hook",
-      ),
-    ];
-
-    return this.uniqueContextLines([
-      ...(hookDebtLines.length > 0 ? hookDebtLines : []),
-      ...fallbackLines,
-    ]).slice(0, 6);
-  }
-
-  private formatAgendaLines(
-    chapterIntent: string,
-    subheading: string,
-    label: string,
-  ): string[] {
-    const hookAgenda = this.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;
-  }
-
-  private collectContextExcerpts(
-    contextPackage: ContextPackage,
-    predicate: (entry: ContextPackage["selectedContext"][number]) => boolean,
-  ): string[] {
-    return this.uniqueContextLines(
-      contextPackage.selectedContext
-        .filter(predicate)
-        .map((entry) => entry.excerpt ?? entry.reason)
-        .filter(Boolean),
-    );
-  }
-
-  private findContextExcerpt(
-    contextPackage: ContextPackage,
-    source: string,
-  ): string | undefined {
-    return contextPackage.selectedContext.find((entry) => entry.source === source)?.excerpt;
-  }
-
-  private renderCompactList(
-    heading: string,
-    items: ReadonlyArray<string>,
-  ): string {
-    if (items.length === 0) {
-      return "";
-    }
-
-    return `${heading}:\n${items.map((item) => `- ${item}`).join("\n")}`;
-  }
-
-  private 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;
+      ruleStack,
+      language,
+    });
   }
   }
 
 
   private buildLengthRequirementBlock(lengthSpec: LengthSpec, language: "zh" | "en"): string {
   private buildLengthRequirementBlock(lengthSpec: LengthSpec, language: "zh" | "en"): string {

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

@@ -0,0 +1,360 @@
+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;
+}