瀏覽代碼

feat(v10): skill distribution — planner rules + writer craft card

V10 Phase 1: Distribute writing methodology across pipeline layers.

Planner (hardcoded rules in injectStructuralSkills):
- S01: Golden chapters (zh: 3, en: 5) → mustKeep with specific chapter instructions
- S02: Paywall zone (en: ch5-8) → mustKeep with cliffhanger requirement
- S03: Mood escalation (no pressure → push for tension) — complements existing de-escalation
- S04: Daily-must-serve-mainline → styleEmphasis when mood is not high
- S10+S11: Desire engine (hook about to resolve → maximize tension before payoff)

Writer (system prompt slimmed from ~4500 to ~800 tokens):
- 9 full methodology modules replaced by compact Writing Craft Card
- Craft card covers: emotion externalization, salt-in-soup, supporting cast,
  five senses, concrete details, sentence craft, desire engine, character
  check, dialogue differentiation, escalation
- Pre-write checklist removed from system prompt (will go to style_guide)
- Golden chapters rules preserved (conditional, already compact)

Bilingual: both ZH and EN craft cards + planner rules.

All 520 tests pass.
Ma 7 月之前
父節點
當前提交
0783173c90

+ 3 - 2
packages/core/src/__tests__/writer-prompts.test.ts

@@ -49,8 +49,9 @@ describe("buildWriterSystemPrompt", () => {
 
     expect(prompt).toContain("## 输入治理契约");
     expect(prompt).toContain("卷纲是默认规划");
-    // Writing methodology is now always injected regardless of mode
-    expect(prompt).toContain("六步走人物心理分析");
+    // v10: compact craft card replaces full methodology modules
+    expect(prompt).toContain("写作铁律");
+    expect(prompt).toContain("盐溶于汤");
     expect(prompt).toContain("黄金3章");
   });
 

+ 91 - 0
packages/core/src/agents/planner.ts

@@ -99,6 +99,25 @@ export class PlannerAgent extends BaseAgent {
       chapterSummaries,
     });
 
+    // ── Skill injection: translate writing methodology into concrete rules ──
+    this.injectStructuralSkills({
+      chapterNumber: input.chapterNumber,
+      language: input.book.language ?? "zh",
+      platform: input.book.platform,
+      mustKeep,
+      mustAvoid,
+      styleEmphasis,
+      hookAgenda,
+      cadence: analyzeChapterCadence({
+        language: this.isChineseLanguage(input.book.language) ? "zh" : "en",
+        rows: parseChapterSummariesMarkdown(chapterSummaries)
+          .filter((s) => s.chapter < input.chapterNumber)
+          .sort((a, b) => a.chapter - b.chapter)
+          .slice(-4)
+          .map((s) => ({ chapter: s.chapter, title: s.title, mood: s.mood, chapterType: s.chapterType })),
+      }),
+    });
+
     const intent = ChapterIntentSchema.parse({
       chapter: input.chapterNumber,
       goal,
@@ -822,4 +841,76 @@ export class PlannerAgent extends BaseAgent {
       return "(文件尚未创建)";
     }
   }
+
+  // ---------------------------------------------------------------------------
+  // Structural skill injection (v10)
+  // Translates writing methodology into concrete mustKeep/styleEmphasis rules.
+  // ---------------------------------------------------------------------------
+
+  private injectStructuralSkills(params: {
+    chapterNumber: number;
+    language: string;
+    platform: string;
+    mustKeep: string[];
+    mustAvoid: string[];
+    styleEmphasis: string[];
+    hookAgenda: { eligibleResolve: ReadonlyArray<string> };
+    cadence: ReturnType<typeof analyzeChapterCadence>;
+  }): void {
+    const ch = params.chapterNumber;
+    const isEn = params.language === "en";
+    const isTomato = params.platform === "tomato";
+
+    // S01: 黄金开篇
+    const goldenLimit = isEn ? 5 : 3;
+    const goldenRulesZh: Record<number, string> = {
+      1: "【黄金第1章】直接进入冲突场景,禁止背景介绍开头。第一段必须有动作或对话。核心矛盾本章浮出水面。最多2个场景3个角色。",
+      2: "【黄金第2章】主角的金手指/核心能力必须初现,通过具体事件展现。第一个小爽点在本章出现。继续收紧核心冲突,不引入新支线。",
+      3: "【黄金第3章】明确具体可衡量的短期目标(打败某人/获得某物/到达某处)。章尾钩子要最强——这是读者决定是否追读的关键章。",
+    };
+    const goldenRulesEn: Record<number, string> = {
+      1: "[Golden Ch1] Drop into conflict — no worldbuilding preamble. First paragraph: action or dialogue. Core conflict surfaces. Max 2 scenes, 3 characters.",
+      2: "[Golden Ch2] Reveal protagonist's edge through a concrete event. First small payoff lands. Tighten core conflict, no new subplots.",
+      3: "[Golden Ch3] Lock in a specific measurable short-term goal. Strong chapter-end hook — make-or-break for retention.",
+      4: "[Golden Ch4] First MAJOR payoff — reward reader investment. Raise emotional stakes: what protagonist stands to LOSE becomes clear.",
+      5: "[Golden Ch5] Raise stakes before paywall — new threat/complication. World expands. Strongest cliffhanger yet — conversion chapter.",
+    };
+    if (ch <= goldenLimit) {
+      const rule = isEn ? goldenRulesEn[ch] : goldenRulesZh[ch];
+      if (rule) params.mustKeep.push(rule);
+    }
+
+    // S02: 付费卡点 / 留存生死线
+    if (isTomato) {
+      // 番茄:免费模式,前3章是编辑签约+读者留存的生死线(和S01重叠)
+      // 不需要额外规则,S01已覆盖
+    } else if (isEn) {
+      // GoodNovel/Dreame:第6-8章付费墙
+      if (ch >= 5 && ch <= 8) {
+        params.mustKeep.push("[Paywall Zone] Reader hits paywall soon — chapter MUST end with an irresistible cliffhanger that makes paying feel inevitable.");
+      }
+    }
+
+    // S04: 日常段服务主线
+    if (params.cadence.moodPressure?.pressure !== "high") {
+      params.styleEmphasis.push(isEn
+        ? "If this chapter has quiet/daily scenes, each must plant a hook or advance a relationship — no pure filler. Every daily scene is bait for the main plot."
+        : '如果本章有日常/过渡段,每段必须埋伏笔或推关系。万物皆为主线的"饵",纯填充即流水账。');
+    }
+
+    // S03: 情绪升压(补充已有的降调逻辑)
+    // 已有 moodDirective 处理连续高压→降调。这里处理反向:无压力时提醒升压
+    if (!params.cadence.moodPressure && ch > goldenLimit) {
+      params.styleEmphasis.push(isEn
+        ? "Recent chapters have been quiet — this chapter needs to escalate tension. Introduce a complication, confrontation, or revelation."
+        : "最近几章情绪偏平,本章必须升压——引入新冲突、对抗或揭秘。");
+    }
+
+    // S10+S11: 信息落差 + 欲望驱动(结合 hook 回收时机)
+    if (params.hookAgenda.eligibleResolve.length > 0) {
+      params.styleEmphasis.push(isEn
+        ? "A major hook is ready to resolve — maximize tension BEFORE the payoff. The release must exceed reader expectations. Satisfy 70% = failure."
+        : "有伏笔即将兑现——在兑现前把压制拉到最大。释放时必须超过读者预期,只满足70%等于失败。");
+    }
+  }
 }

+ 42 - 11
packages/core/src/agents/writer-prompts.ts

@@ -45,8 +45,7 @@ export function buildWriterSystemPrompt(
         buildEnglishCoreRules(book),
         buildGovernedInputContract("en", governed),
         buildLengthGuidance(resolvedLengthSpec, "en"),
-        buildEnglishAntiAIRules(),
-        buildEnglishCharacterMethod(),
+        buildWritingCraftCard("en"),
         buildGenreRules(genreProfile, genreBody),
         buildProtagonistRules(bookRules),
         buildBookRulesBody(bookRulesBody),
@@ -55,7 +54,7 @@ export function buildWriterSystemPrompt(
         fanficContext ? buildFanficCanonSection(fanficContext.fanficCanon, fanficContext.fanficMode) : "",
         fanficContext ? buildCharacterVoiceProfiles(fanficContext.fanficCanon) : "",
         fanficContext ? buildFanficModeInstructions(fanficContext.fanficMode, fanficContext.allowedDeviations) : "",
-        buildEnglishPreWriteChecklist(book, genreProfile),
+        // Pre-write checklist moved to style_guide.md (v10)
         outputSection,
       ]
     : [
@@ -63,12 +62,7 @@ export function buildWriterSystemPrompt(
         buildCoreRules(resolvedLengthSpec),
         buildGovernedInputContract("zh", governed),
         buildLengthGuidance(resolvedLengthSpec, "zh"),
-        buildAntiAIExamples(),
-        buildCharacterPsychologyMethod(),
-        buildSupportingCharacterMethod(),
-        buildReaderPsychologyMethod(),
-        buildEmotionalPacingMethod(),
-        buildImmersionTechniques(),
+        buildWritingCraftCard("zh"),
         buildGoldenChaptersRules(chapterNumber, isEnglish ? "en" : "zh"),
         bookRules?.enableFullCastTracking ? buildFullCastTracking() : "",
         buildGenreRules(genreProfile, genreBody),
@@ -79,7 +73,7 @@ export function buildWriterSystemPrompt(
         fanficContext ? buildFanficCanonSection(fanficContext.fanficCanon, fanficContext.fanficMode) : "",
         fanficContext ? buildCharacterVoiceProfiles(fanficContext.fanficCanon) : "",
         fanficContext ? buildFanficModeInstructions(fanficContext.fanficMode, fanficContext.allowedDeviations) : "",
-        buildPreWriteChecklist(book, genreProfile),
+        // Pre-write checklist moved to style_guide.md (v10)
         outputSection,
       ];
 
@@ -360,7 +354,44 @@ function buildImmersionTechniques(): string {
 }
 
 // ---------------------------------------------------------------------------
-// 黄金三章(前3章特殊指令)
+// Writing Craft Card (v10: compact rules, replaces 9 full modules)
+// Full methodology is in style_guide.md; this is the always-on reminder.
+// ---------------------------------------------------------------------------
+
+function buildWritingCraftCard(language: "zh" | "en"): string {
+  if (language === "en") {
+    return `## Writing Craft Rules
+
+- **Emotion**: Externalize through action — never write "he felt angry", write "he crushed the teacup"
+- **Salt in soup**: Values conveyed through behavior, not slogans
+- **Supporting cast**: Every side character has their own agenda. Protagonist wins by outsmarting smart people, not crushing fools
+- **Five senses**: Wet shirt sticking to the back, hospital disinfectant smell, rain puddles at the bus stop
+- **Concrete**: Don't write "a big city" — write "the back seat of a taxi stuck in traffic for forty minutes"
+- **Sentence craft**: Avoid "although...however" / "nevertheless" / excessive "was". Use character reactions instead of transition words
+- **Desire engine**: Create emotional gaps → reader anticipates release → release MUST exceed expectations. 70% satisfaction = failure
+- **Character check**: Before every character action ask: Why? Does it match their profile? Would the reader find it jarring?
+- **Dialogue**: Different characters speak differently — vocabulary, sentence length, verbal tics, dialect traces
+- **Forbidden**: Info-dump character introductions / introducing 3+ new characters at once / "everyone gasped in unison"
+- **Escalation**: Bad things stack — each layer worse than the last. Not one setback, but setback → worse setback → even worse`;
+  }
+
+  return `## 写作铁律
+
+- **情绪**:用动作外化,不写"他感到愤怒",写"他捏碎了茶杯,滚烫的茶水流过指缝"
+- **盐溶于汤**:价值观通过行为传达,不喊口号
+- **配角**:有自己的算盘和反击,主角压服聪明人不是碾压傻子
+- **五感**:潮湿的短袖黏在后背上、医院消毒水的味、雨天公交站的积水
+- **具体化**:不写"大城市",写"三环堵了四十分钟的出租车后座"
+- **句式**:少用"虽然但是/然而/因此/了",用角色内心吐槽替代转折词
+- **欲望驱动**:制造情绪缺口→读者期待释放→释放时超过预期。满足70%等于失败
+- **人设三问**:为什么这么做?符合人设吗?读者会觉得突兀吗?
+- **对话**:不同角色说话方式不同——用词习惯、句子长短、口头禅、方言痕迹
+- **禁止**:资料卡式介绍角色 / 一次引入超3个新角色 / 众人齐声惊呼
+- **升级**:坏事叠坏事,每层比上一层过分——被骂→手机掉了→直播课结束了→包子噎住了`;
+}
+
+// ---------------------------------------------------------------------------
+// 黄金开篇(中文3章/英文5章)
 // ---------------------------------------------------------------------------
 
 function buildGoldenChaptersRules(chapterNumber?: number, language?: string): string {

二進制
微信图片_20260408193050_10_397.jpg