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

fix(core): stop writer over-fragmenting paragraphs under rule 1-3 density

bench8 审计显示三本书的 13/15 章都被 paragraph-shape 警告刷屏
(重生 1 章 76/106 段 <35 字; 港片 1 章 64/98; 渡劫多章连续 >5 短段).
写手把"每 300 字 1 爽点 + 章末断在 action-climax"过度解读成
"段落切得越短越好", 把一句动作/一句台词/一句反应各自切成孤立短段堆砌节奏感.

writer-prompts.ts 看点密集度段补一条反向约束:
- 明确密度靠段落内语义密度, 不靠切段落
- 复述已有的 3-5 行 / 40-120 字段落上限
- 爆点短段允许独立成段但一章 ≤ 5 个
- 挑明 reviewer 会拒绝的阈值 (50+ 段 <35 字 / 连续 5+ 短段)

hook-ledger-validator.ts 顺手修一个 tsc build 报错:
parseHookLedger 里 current 的类型从内联 union 改为命名 Subsection 类型别名,
原写法 "as typeof current" 在 current 包含 null 时 tsc 判不兼容.
逻辑无变化; 核心 933/933 测试继续通过.
Ma 6 месяцев назад
Родитель
Сommit
b1c7089f10

+ 1 - 0
packages/core/src/agents/writer-prompts.ts

@@ -219,6 +219,7 @@ function buildCoreRules(lengthSpec: LengthSpec): string {
 - **每 1000-1500 字至少 1 个完整悬念**:一组"问题—蓄力—未解"的结构,给读者追下去的理由
 - 不靠密度堆砌糊弄——单章里的爽点/钩子/悬念必须服务于本章 goal,不能是和主线无关的孤立段落
 - 如果某段连续 300 字以上是环境、回忆、议论、心理独白而没有推进主线或制造看点,就是水文,必须删或改
+- **密度不是靠切段落实现——是靠段落内的语义密度实现**:每段仍维持 3-5 行手机阅读长度(约 40-120 字),禁止把一句动作、一句台词、一句反应各自切成孤立短段堆砌节奏感。只有需要单独承重的"爆点短段"(一击命中、一句改变局势的台词、一个定格动作)才允许独立成段,一章里这类爆点短段总数不超过 5 个。审核会拒绝:100 段里 50+ 段 <35 字 / 连续出现 5 个以上 <35 字段落。
 
 ## 章节 80/20 断章(番茄老师弈青锋,硬尺)
 

+ 4 - 3
packages/core/src/utils/hook-ledger-validator.ts

@@ -67,7 +67,8 @@ export function parseHookLedger(memoBody: string): HookLedger {
     return { open: [], advance: [], resolve: [], defer: [], newOpenCount: 0 };
   }
 
-  const result: Record<"open" | "advance" | "resolve" | "defer", HookLedgerEntry[]> = {
+  type Subsection = "open" | "advance" | "resolve" | "defer";
+  const result: Record<Subsection, HookLedgerEntry[]> = {
     open: [],
     advance: [],
     resolve: [],
@@ -75,14 +76,14 @@ export function parseHookLedger(memoBody: string): HookLedger {
   };
   let newOpenCount = 0;
 
-  let current: "open" | "advance" | "resolve" | "defer" | null = null;
+  let current: Subsection | null = null;
   for (const rawLine of section.split(/\r?\n/)) {
     const line = rawLine.trim();
     if (line.length === 0) continue;
 
     const subHeadingMatch = line.match(/^(open|advance|resolve|defer)\s*[::]?\s*$/i);
     if (subHeadingMatch) {
-      current = subHeadingMatch[1]!.toLowerCase() as typeof current;
+      current = subHeadingMatch[1]!.toLowerCase() as Subsection;
       continue;
     }