Просмотр исходного кода

feat: add settler prompt and parser modules

Settler handles state settlement (truth file updates) as a separate
phase from creative writing, with dedicated prompts and a parser
that extracts the 8 settlement output blocks.
Ma 8 месяцев назад
Родитель
Сommit
3a965e4743

+ 38 - 0
packages/core/src/agents/settler-parser.ts

@@ -0,0 +1,38 @@
+import type { GenreProfile } from "../models/genre-profile.js";
+
+export interface SettlementOutput {
+  readonly postSettlement: string;
+  readonly updatedState: string;
+  readonly updatedLedger: string;
+  readonly updatedHooks: string;
+  readonly chapterSummary: string;
+  readonly updatedSubplots: string;
+  readonly updatedEmotionalArcs: string;
+  readonly updatedCharacterMatrix: string;
+}
+
+export function parseSettlementOutput(
+  content: string,
+  genreProfile: GenreProfile,
+): SettlementOutput {
+  const extract = (tag: string): string => {
+    const regex = new RegExp(
+      `=== ${tag} ===\\s*([\\s\\S]*?)(?==== [A-Z_]+ ===|$)`,
+    );
+    const match = content.match(regex);
+    return match?.[1]?.trim() ?? "";
+  };
+
+  return {
+    postSettlement: extract("POST_SETTLEMENT"),
+    updatedState: extract("UPDATED_STATE") || "(状态卡未更新)",
+    updatedLedger: genreProfile.numericalSystem
+      ? (extract("UPDATED_LEDGER") || "(账本未更新)")
+      : "",
+    updatedHooks: extract("UPDATED_HOOKS") || "(伏笔池未更新)",
+    chapterSummary: extract("CHAPTER_SUMMARY"),
+    updatedSubplots: extract("UPDATED_SUBPLOTS"),
+    updatedEmotionalArcs: extract("UPDATED_EMOTIONAL_ARCS"),
+    updatedCharacterMatrix: extract("UPDATED_CHARACTER_MATRIX"),
+  };
+}

+ 175 - 0
packages/core/src/agents/settler-prompts.ts

@@ -0,0 +1,175 @@
+import type { BookConfig } from "../models/book.js";
+import type { GenreProfile } from "../models/genre-profile.js";
+import type { BookRules } from "../models/book-rules.js";
+
+export function buildSettlerSystemPrompt(
+  book: BookConfig,
+  genreProfile: GenreProfile,
+  bookRules: BookRules | null,
+): string {
+  const numericalBlock = genreProfile.numericalSystem
+    ? `\n- 本题材有数值/资源体系,你必须在 UPDATED_LEDGER 中追踪正文中出现的所有资源变动
+- 数值验算铁律:期初 + 增量 = 期末,三项必须可验算`
+    : `\n- 本题材无数值系统,UPDATED_LEDGER 留空`;
+
+  const hookRules = `
+## 伏笔追踪规则
+
+- 新伏笔:正文中出现的暗示、悬念、未解之谜 → 新增 hook_id,标注起始章、类型、状态=待定
+- 推进伏笔:已有伏笔在本章有新进展 → 更新"最近推进"列和状态
+- 回收伏笔:伏笔在本章明确揭示/解决 → 状态改为"已回收"
+- 延后伏笔:超过5章未推进 → 标注"延后",备注原因`;
+
+  const fullCastBlock = bookRules?.enableFullCastTracking
+    ? `\n## 全员追踪\nPOST_SETTLEMENT 必须额外包含:本章出场角色清单、角色间关系变动、未出场但被提及的角色。`
+    : "";
+
+  return `你是状态追踪分析师。给定新章节正文和当前 truth 文件,你的任务是产出更新后的 truth 文件。
+
+## 工作模式
+
+你不是在写作。你的任务是:
+1. 仔细阅读正文,提取所有状态变化
+2. 基于"当前追踪文件"做增量更新
+3. 严格按照 === TAG === 格式输出
+
+## 分析维度
+
+从正文中提取以下信息:
+- 角色出场、退场、状态变化(受伤/突破/死亡等)
+- 位置移动、场景转换
+- 物品/资源的获得与消耗
+- 伏笔的埋设、推进、回收
+- 情感弧线变化
+- 支线进展
+- 角色间关系变化、新的信息边界
+
+## 书籍信息
+
+- 标题:${book.title}
+- 题材:${genreProfile.name}(${book.genre})
+- 平台:${book.platform}
+${numericalBlock}
+${hookRules}${fullCastBlock}
+
+## 输出格式(必须严格遵循)
+
+${buildSettlerOutputFormat(genreProfile)}
+
+## 关键规则
+
+1. 状态卡和伏笔池必须基于"当前追踪文件"做增量更新,不是从零开始
+2. 正文中的每一个事实性变化都必须反映在对应的追踪文件中
+3. 不要遗漏细节:数值变化、位置变化、关系变化、信息变化都要记录
+4. 角色交互矩阵中的"信息边界"要准确——角色只知道他在场时发生的事`;
+}
+
+function buildSettlerOutputFormat(gp: GenreProfile): string {
+  const postSettlement = gp.numericalSystem
+    ? `=== POST_SETTLEMENT ===
+(如有数值变动,必须输出Markdown表格)
+| 结算项 | 本章记录 | 备注 |
+|--------|----------|------|
+| 资源账本 | 期初X / 增量+Y / 期末Z | 无增量写+0 |
+| 重要资源 | 资源名 -> 贡献+Y(依据) | 无写"无" |
+| 伏笔变动 | 新增/回收/延后 Hook | 同步更新伏笔池 |`
+    : `=== POST_SETTLEMENT ===
+(如有伏笔变动,必须输出)
+| 结算项 | 本章记录 | 备注 |
+|--------|----------|------|
+| 伏笔变动 | 新增/回收/延后 Hook | 同步更新伏笔池 |`;
+
+  const updatedLedger = gp.numericalSystem
+    ? `\n=== UPDATED_LEDGER ===\n(更新后的完整资源账本,Markdown表格格式)`
+    : "";
+
+  return `${postSettlement}
+
+=== UPDATED_STATE ===
+(更新后的完整状态卡,Markdown表格格式)
+${updatedLedger}
+=== UPDATED_HOOKS ===
+(更新后的完整伏笔池,Markdown表格格式)
+
+=== CHAPTER_SUMMARY ===
+(本章摘要,Markdown表格格式,必须包含以下列)
+| 章节 | 标题 | 出场人物 | 关键事件 | 状态变化 | 伏笔动态 | 情绪基调 | 章节类型 |
+|------|------|----------|----------|----------|----------|----------|----------|
+| N | 本章标题 | 角色1,角色2 | 一句话概括 | 关键变化 | H01埋设/H02推进 | 情绪走向 | ${gp.chapterTypes.length > 0 ? gp.chapterTypes.join("/") : "过渡/冲突/高潮/收束"} |
+
+=== UPDATED_SUBPLOTS ===
+(更新后的完整支线进度板,Markdown表格格式)
+| 支线ID | 支线名 | 相关角色 | 起始章 | 最近活跃章 | 距今章数 | 状态 | 进度概述 | 回收ETA |
+|--------|--------|----------|--------|------------|----------|------|----------|---------|
+
+=== UPDATED_EMOTIONAL_ARCS ===
+(更新后的完整情感弧线,Markdown表格格式)
+| 角色 | 章节 | 情绪状态 | 触发事件 | 强度(1-10) | 弧线方向 |
+|------|------|----------|----------|------------|----------|
+
+=== UPDATED_CHARACTER_MATRIX ===
+(更新后的角色交互矩阵,分三个子表)
+
+### 角色档案
+| 角色 | 核心标签 | 反差细节 | 说话风格 | 性格底色 | 与主角关系 | 核心动机 | 当前目标 |
+|------|----------|----------|----------|----------|------------|----------|----------|
+
+### 相遇记录
+| 角色A | 角色B | 首次相遇章 | 最近交互章 | 关系性质 | 关系变化 |
+|-------|-------|------------|------------|----------|----------|
+
+### 信息边界
+| 角色 | 已知信息 | 未知信息 | 信息来源章 |
+|------|----------|----------|------------|`;
+}
+
+export function buildSettlerUserPrompt(params: {
+  readonly chapterNumber: number;
+  readonly title: string;
+  readonly content: string;
+  readonly currentState: string;
+  readonly ledger: string;
+  readonly hooks: string;
+  readonly chapterSummaries: string;
+  readonly subplotBoard: string;
+  readonly emotionalArcs: string;
+  readonly characterMatrix: string;
+  readonly volumeOutline: string;
+}): string {
+  const ledgerBlock = params.ledger
+    ? `\n## 当前资源账本\n${params.ledger}\n`
+    : "";
+
+  const summariesBlock = params.chapterSummaries !== "(文件尚未创建)"
+    ? `\n## 已有章节摘要\n${params.chapterSummaries}\n`
+    : "";
+
+  const subplotBlock = params.subplotBoard !== "(文件尚未创建)"
+    ? `\n## 当前支线进度板\n${params.subplotBoard}\n`
+    : "";
+
+  const emotionalBlock = params.emotionalArcs !== "(文件尚未创建)"
+    ? `\n## 当前情感弧线\n${params.emotionalArcs}\n`
+    : "";
+
+  const matrixBlock = params.characterMatrix !== "(文件尚未创建)"
+    ? `\n## 当前角色交互矩阵\n${params.characterMatrix}\n`
+    : "";
+
+  return `请分析第${params.chapterNumber}章「${params.title}」的正文,更新所有追踪文件。
+
+## 本章正文
+
+${params.content}
+
+## 当前状态卡
+${params.currentState}
+${ledgerBlock}
+## 当前伏笔池
+${params.hooks}
+${summariesBlock}${subplotBlock}${emotionalBlock}${matrixBlock}
+## 卷纲
+${params.volumeOutline}
+
+请严格按照 === TAG === 格式输出结算结果。`;
+}

+ 2 - 0
packages/core/src/index.ts

@@ -29,6 +29,8 @@ export { analyzeDetectionInsights } from "./agents/detection-insights.js";
 export { validatePostWrite, type PostWriteViolation } from "./agents/post-write-validator.js";
 export { ChapterAnalyzerAgent, type AnalyzeChapterInput, type AnalyzeChapterOutput } from "./agents/chapter-analyzer.js";
 export { parseWriterOutput, type ParsedWriterOutput } from "./agents/writer-parser.js";
+export { buildSettlerSystemPrompt, buildSettlerUserPrompt } from "./agents/settler-prompts.js";
+export { parseSettlementOutput, type SettlementOutput } from "./agents/settler-parser.js";
 
 // Utils
 export { fetchUrl } from "./utils/web-search.js";