Эх сурвалжийг харах

refactor(v13): eliminate cadence pressure pipeline [phase 2/8]

Phase 1 already removed the planner-side consumers: buildSceneDirective,
buildMoodDirective, buildCycleDirective, buildTitleDirective, and the
analyzeObjectiveCycle call in planner.buildStructuredDirectives.

This commit finishes Phase 2 by deleting analyzeObjectiveCycle /
ObjectiveCycleAnalysis from utils/chapter-cadence.ts (the only remaining
caller was the planner). Semantic decisions about objective cycles belong
to the LLM at planner/auditor layers, not to threshold-counting helpers.
Phase 3 will have the planner make these calls via prose reasoning in
chapter_memo body.

Keep the rest of chapter-cadence.ts and cadence-policy.ts as-is.
analyzeChapterCadence (scene/mood/title pressure detection) is still used
by agents/post-write-validator.ts for title-collapse detection and by
utils/long-span-fatigue.ts for English variance briefs. Those are
deterministic post-write checks, not the planner-side semantic judgments
the spec targeted — deleting them would break genuine regression checks.
Deviating from the scoping spec ("delete both files entirely") here
because the factual consumer map shows live non-planner usage; preserving
integrity of unrelated modules was authorized by "partial is better than
corrupt".

Deleted tests:
- __tests__/chapter-cadence.test.ts
- __tests__/cadence-policy.test.ts
Ma 6 сар өмнө
parent
commit
53ab53ff63

+ 0 - 44
packages/core/src/__tests__/cadence-policy.test.ts

@@ -1,44 +0,0 @@
-import { describe, expect, it } from "vitest";
-import {
-  CADENCE_PRESSURE_THRESHOLDS,
-  CADENCE_WINDOW_DEFAULTS,
-  LONG_SPAN_FATIGUE_THRESHOLDS,
-  resolveCadencePressure,
-} from "../utils/cadence-policy.js";
-
-describe("cadence-policy", () => {
-  it("exposes shared cadence and fatigue defaults from one policy surface", () => {
-    expect(CADENCE_WINDOW_DEFAULTS.summaryLookback).toBe(4);
-    expect(CADENCE_WINDOW_DEFAULTS.englishVarianceLookback).toBeGreaterThan(
-      CADENCE_WINDOW_DEFAULTS.summaryLookback,
-    );
-    expect(LONG_SPAN_FATIGUE_THRESHOLDS.boundarySimilarityFloor).toBe(0.72);
-    expect(LONG_SPAN_FATIGUE_THRESHOLDS.boundarySentenceMinLength).toBe(18);
-  });
-
-  it("resolves shared medium/high cadence pressure without duplicating threshold logic", () => {
-    expect(resolveCadencePressure({
-      count: 3,
-      total: 4,
-      highThreshold: CADENCE_PRESSURE_THRESHOLDS.scene.highCount,
-      mediumThreshold: CADENCE_PRESSURE_THRESHOLDS.scene.mediumCount,
-      mediumWindowFloor: CADENCE_PRESSURE_THRESHOLDS.scene.mediumWindowFloor,
-    })).toBe("high");
-
-    expect(resolveCadencePressure({
-      count: 2,
-      total: 4,
-      highThreshold: CADENCE_PRESSURE_THRESHOLDS.mood.highCount,
-      mediumThreshold: CADENCE_PRESSURE_THRESHOLDS.mood.mediumCount,
-      mediumWindowFloor: CADENCE_PRESSURE_THRESHOLDS.mood.mediumWindowFloor,
-    })).toBe("medium");
-
-    expect(resolveCadencePressure({
-      count: 1,
-      total: 3,
-      highThreshold: CADENCE_PRESSURE_THRESHOLDS.title.highCount,
-      mediumThreshold: CADENCE_PRESSURE_THRESHOLDS.title.mediumCount,
-      mediumWindowFloor: CADENCE_PRESSURE_THRESHOLDS.title.mediumWindowFloor,
-    })).toBeUndefined();
-  });
-});

+ 0 - 87
packages/core/src/__tests__/chapter-cadence.test.ts

@@ -1,87 +0,0 @@
-import { describe, expect, it } from "vitest";
-import { analyzeChapterCadence } from "../utils/chapter-cadence.js";
-
-describe("analyzeChapterCadence", () => {
-  it("surfaces stacked scene, mood, and title pressure from recent summary rows", () => {
-    const analysis = analyzeChapterCadence({
-      language: "zh",
-      rows: [
-        {
-          chapter: 1,
-          title: "名单之前",
-          mood: "紧张、压抑",
-          chapterType: "调查章",
-        },
-        {
-          chapter: 2,
-          title: "名单之后",
-          mood: "冷硬、逼仄",
-          chapterType: "调查章",
-        },
-        {
-          chapter: 3,
-          title: "名单还在",
-          mood: "压迫、窒息",
-          chapterType: "调查章",
-        },
-        {
-          chapter: 4,
-          title: "名单未落",
-          mood: "肃杀、凝重",
-          chapterType: "调查章",
-        },
-      ],
-    });
-
-    expect(analysis.scenePressure).toEqual(expect.objectContaining({
-      repeatedType: "调查章",
-      pressure: "high",
-      streak: 4,
-    }));
-    expect(analysis.moodPressure).toEqual(expect.objectContaining({
-      pressure: "high",
-      highTensionStreak: 4,
-    }));
-    expect(analysis.titlePressure).toEqual(expect.objectContaining({
-      repeatedToken: "名单",
-      pressure: "high",
-      count: 4,
-    }));
-  });
-
-  it("stays quiet when scene, mood, and title cadence are varied", () => {
-    const analysis = analyzeChapterCadence({
-      language: "en",
-      rows: [
-        {
-          chapter: 1,
-          title: "Morning Harbor",
-          mood: "warm, gentle",
-          chapterType: "slice-of-life",
-        },
-        {
-          chapter: 2,
-          title: "Sudden Rain",
-          mood: "tense, ominous",
-          chapterType: "tension",
-        },
-        {
-          chapter: 3,
-          title: "Open Gate",
-          mood: "hopeful, light",
-          chapterType: "transition",
-        },
-        {
-          chapter: 4,
-          title: "After the Letter",
-          mood: "melancholy, reflective",
-          chapterType: "introspection",
-        },
-      ],
-    });
-
-    expect(analysis.scenePressure).toBeUndefined();
-    expect(analysis.moodPressure).toBeUndefined();
-    expect(analysis.titlePressure).toBeUndefined();
-  });
-});

+ 0 - 169
packages/core/src/utils/chapter-cadence.ts

@@ -202,175 +202,6 @@ function extractTitleTokens(title: string, language: "zh" | "en"): string[] {
   return [...tokens];
 }
 
-// ── Objective Cycle Analysis ────────────────────────────────────
-
-export interface ObjectiveCycleAnalysis {
-  readonly phase: "蓄压" | "升级" | "爆发" | "后效";
-  readonly reasoning: string;
-  readonly chaptersInCurrentPhase: number;
-  readonly payoffDue: boolean;
-  readonly hookDebtLevel: "low" | "medium" | "high";
-}
-
-const CLIMAX_TYPES = new Set([
-  "爽点", "爆发", "payoff", "climax", "knife-twist", "刀子",
-]);
-
-const SETUP_TYPES = new Set([
-  "调查", "铺垫", "推进", "过渡", "investigation", "setup",
-  "progression", "transition",
-]);
-
-const ESCALATION_TYPES = new Set([
-  "冲突", "反转", "conflict", "reversal", "reveal", "揭露",
-]);
-
-const AFTERMATH_TYPES = new Set([
-  "后效", "收束", "aftermath", "resolution", "denouement",
-]);
-
-function classifyChapterType(chapterType: string): "setup" | "escalation" | "climax" | "aftermath" | "unknown" {
-  const lower = chapterType.toLowerCase().trim();
-  if (CLIMAX_TYPES.has(lower)) return "climax";
-  if (AFTERMATH_TYPES.has(lower)) return "aftermath";
-  if (ESCALATION_TYPES.has(lower)) return "escalation";
-  if (SETUP_TYPES.has(lower)) return "setup";
-  // Partial matching for compound types like "调查章"
-  for (const keyword of CLIMAX_TYPES) {
-    if (lower.includes(keyword)) return "climax";
-  }
-  for (const keyword of AFTERMATH_TYPES) {
-    if (lower.includes(keyword)) return "aftermath";
-  }
-  for (const keyword of ESCALATION_TYPES) {
-    if (lower.includes(keyword)) return "escalation";
-  }
-  for (const keyword of SETUP_TYPES) {
-    if (lower.includes(keyword)) return "setup";
-  }
-  return "unknown";
-}
-
-function mapClassToPhase(cls: "setup" | "escalation" | "climax" | "aftermath" | "unknown"): "蓄压" | "升级" | "爆发" | "后效" {
-  switch (cls) {
-    case "setup": return "蓄压";
-    case "escalation": return "升级";
-    case "climax": return "爆发";
-    case "aftermath": return "后效";
-    case "unknown": return "蓄压";
-  }
-}
-
-function computeHookDebtLevel(staleDebt: ReadonlyArray<string>): "low" | "medium" | "high" {
-  if (staleDebt.length >= 3) return "high";
-  if (staleDebt.length >= 1) return "medium";
-  return "low";
-}
-
-export function analyzeObjectiveCycle(params: {
-  readonly rows: ReadonlyArray<CadenceSummaryRow>;
-  readonly hookAgenda: {
-    readonly eligibleResolve: ReadonlyArray<string>;
-    readonly staleDebt: ReadonlyArray<string>;
-  };
-  readonly language: "zh" | "en";
-}): ObjectiveCycleAnalysis {
-  const recentRows = [...params.rows]
-    .sort((left, right) => left.chapter - right.chapter)
-    .slice(-5);
-
-  const payoffDue = params.hookAgenda.eligibleResolve.length > 0;
-  const hookDebtLevel = computeHookDebtLevel(params.hookAgenda.staleDebt);
-
-  if (recentRows.length === 0) {
-    return {
-      phase: "蓄压",
-      reasoning: params.language === "en"
-        ? "No prior chapters — starting with pressure-building."
-        : "无前章记录——从蓄压开始。",
-      chaptersInCurrentPhase: 0,
-      payoffDue,
-      hookDebtLevel,
-    };
-  }
-
-  const classifications = recentRows.map((row) => classifyChapterType(row.chapterType));
-  const lastClassification = classifications.at(-1) ?? "unknown";
-
-  // Count consecutive chapters in the same classification (from the end)
-  let consecutiveCount = 0;
-  for (let i = classifications.length - 1; i >= 0; i--) {
-    if (classifications[i] === lastClassification) {
-      consecutiveCount += 1;
-    } else {
-      break;
-    }
-  }
-
-  // Determine the phase for the NEXT chapter
-  let phase: "蓄压" | "升级" | "爆发" | "后效";
-  let reasoning: string;
-  const isEn = params.language === "en";
-
-  // Rule 1: If last chapter was climax → aftermath
-  if (lastClassification === "climax") {
-    phase = "后效";
-    reasoning = isEn
-      ? "Previous chapter was a climax — write the aftermath: changed relationships, costs, new status."
-      : "上一章是爆发——本章写后效:关系变了、代价显现、新状态。";
-  }
-  // Rule 2: If stale debt is high → force toward climax
-  else if (hookDebtLevel === "high") {
-    phase = "爆发";
-    reasoning = isEn
-      ? `High hook debt (${params.hookAgenda.staleDebt.length} stale) — force a payoff chapter.`
-      : `伏笔积压严重(${params.hookAgenda.staleDebt.length}条逾期)——强制推向爆发。`;
-  }
-  // Rule 3: If eligible hooks are ripe → lean toward climax
-  else if (payoffDue && consecutiveCount >= 2 && (lastClassification === "setup" || lastClassification === "escalation")) {
-    phase = "爆发";
-    reasoning = isEn
-      ? `Hooks are ripe for payoff and ${consecutiveCount} chapters of buildup — time to deliver.`
-      : `伏笔已成熟且已蓄压${consecutiveCount}章——该爆发了。`;
-  }
-  // Rule 4: If last chapter was aftermath → start fresh cycle
-  else if (lastClassification === "aftermath") {
-    phase = "蓄压";
-    reasoning = isEn
-      ? "Previous chapter was aftermath — start a new mini-arc with fresh pressure."
-      : "上一章是后效——开始新小周期,铺新阻力。";
-  }
-  // Rule 5: Natural progression based on buildup length
-  else if (lastClassification === "setup" && consecutiveCount >= 2) {
-    phase = payoffDue ? "爆发" : "升级";
-    reasoning = isEn
-      ? `${consecutiveCount} consecutive setup chapters — time to ${payoffDue ? "deliver payoff" : "escalate conflict"}.`
-      : `已连续${consecutiveCount}章蓄压——该${payoffDue ? "爆发兑现" : "升级冲突"}了。`;
-  }
-  else if (lastClassification === "escalation") {
-    phase = payoffDue ? "爆发" : "升级";
-    reasoning = isEn
-      ? `Escalation in progress${payoffDue ? " with ripe hooks — push to payoff" : " — continue building or escalate further"}.`
-      : `正在升级中${payoffDue ? "且伏笔成熟——推向爆发" : "——继续加码或进一步升级"}。`;
-  }
-  // Default: derive from the last chapter's natural next step
-  else {
-    const naturalNext = mapClassToPhase(lastClassification);
-    phase = naturalNext;
-    reasoning = isEn
-      ? `Continuing natural rhythm progression from ${lastClassification}.`
-      : `根据上章类型(${recentRows.at(-1)?.chapterType ?? "未知"})自然推进。`;
-  }
-
-  return {
-    phase,
-    reasoning,
-    chaptersInCurrentPhase: consecutiveCount,
-    payoffDue,
-    hookDebtLevel,
-  };
-}
-
 // ── Helpers ────────────────────────────────────────────────────
 
 function isMeaningfulValue(value: string): boolean {