Sfoglia il codice sorgente

refactor(core): 读架构稿硬路径统一改用 outline-paths helper

writer / continuity / reviser / chapter-analyzer / consolidator / planner /
runner / memory-retrieval 里读 story_bible.md / volume_outline.md /
character_matrix.md / current_state.md 的点都改成 readStoryFrame /
readVolumeMap / readCharacterContext / readCurrentStateWithFallback,
让 Phase 5 新书走 outline/ + roles/ 权威路径,旧书自动 fallback 到 legacy。

runner.ts 里做 before/after 对比的 old state 读取保留原样(需要磁盘原始内容);
book_rules.md 没有新路径替代,所有点也保留原 readFile 调用。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fanghanjun 6 mesi fa
parent
commit
0b690abfdb

+ 5 - 4
packages/core/src/agents/chapter-analyzer.ts

@@ -3,6 +3,7 @@ import type { BookConfig } from "../models/book.js";
 import type { GenreProfile } from "../models/genre-profile.js";
 import type { ContextPackage, RuleStack } from "../models/input-governance.js";
 import { readGenreProfile, readBookRules } from "./rules-reader.js";
+import { readStoryFrame, readVolumeMap, readCharacterContext, readCurrentStateWithFallback } from "../utils/outline-paths.js";
 import { parseWriterOutput, type ParsedWriterOutput } from "./writer-parser.js";
 import { buildGovernedMemoryEvidenceBlocks } from "../utils/governed-context.js";
 import {
@@ -45,14 +46,14 @@ export class ChapterAnalyzerAgent extends BaseAgent {
       subplotBoard, emotionalArcs, characterMatrix,
       storyBible, volumeOutline,
     ] = await Promise.all([
-      this.readFileOrDefault(join(bookDir, "story/current_state.md"), resolvedLanguage),
+      readCurrentStateWithFallback(bookDir),
       this.readFileOrDefault(join(bookDir, "story/particle_ledger.md"), resolvedLanguage),
       this.readFileOrDefault(join(bookDir, "story/pending_hooks.md"), resolvedLanguage),
       this.readFileOrDefault(join(bookDir, "story/subplot_board.md"), resolvedLanguage),
       this.readFileOrDefault(join(bookDir, "story/emotional_arcs.md"), resolvedLanguage),
-      this.readFileOrDefault(join(bookDir, "story/character_matrix.md"), resolvedLanguage),
-      this.readFileOrDefault(join(bookDir, "story/story_bible.md"), resolvedLanguage),
-      this.readFileOrDefault(join(bookDir, "story/volume_outline.md"), resolvedLanguage),
+      readCharacterContext(bookDir),
+      readStoryFrame(bookDir),
+      readVolumeMap(bookDir),
     ]);
     const parsedBookRules = await readBookRules(bookDir);
     const bookRulesBody = parsedBookRules?.body ?? "";

+ 2 - 2
packages/core/src/agents/consolidator.ts

@@ -1,6 +1,7 @@
 import { BaseAgent } from "./base.js";
 import { readFile, writeFile, mkdir } from "node:fs/promises";
 import { join } from "node:path";
+import { readVolumeMap } from "../utils/outline-paths.js";
 
 export interface ConsolidationResult {
   readonly volumeSummaries: string;
@@ -26,12 +27,11 @@ export class ConsolidatorAgent extends BaseAgent {
   async consolidate(bookDir: string): Promise<ConsolidationResult> {
     const storyDir = join(bookDir, "story");
     const summariesPath = join(storyDir, "chapter_summaries.md");
-    const outlinePath = join(storyDir, "volume_outline.md");
     const volumeSummariesPath = join(storyDir, "volume_summaries.md");
 
     const [summariesRaw, outlineRaw] = await Promise.all([
       readFile(summariesPath, "utf-8").catch(() => ""),
-      readFile(outlinePath, "utf-8").catch(() => ""),
+      readVolumeMap(bookDir),
     ]);
 
     if (!summariesRaw || !outlineRaw) {

+ 4 - 3
packages/core/src/agents/continuity.ts

@@ -6,6 +6,7 @@ import type { ContextPackage, RuleStack } from "../models/input-governance.js";
 import { readGenreProfile, readBookLanguage, readBookRules } from "./rules-reader.js";
 import { getFanficDimensionConfig, FANFIC_DIMENSIONS } from "./fanfic-dimensions.js";
 import { readFile, readdir } from "node:fs/promises";
+import { readCurrentStateWithFallback, readCharacterContext, readVolumeMap } from "../utils/outline-paths.js";
 import { filterHooks, filterSummaries, filterSubplots, filterEmotionalArcs, filterCharacterMatrix } from "../utils/context-filter.js";
 import { buildGovernedMemoryEvidenceBlocks } from "../utils/governed-context.js";
 import { join } from "node:path";
@@ -334,17 +335,17 @@ export class ContinuityAuditor extends BaseAgent {
   ): Promise<AuditResult> {
     const [diskCurrentState, diskLedger, diskHooks, styleGuideRaw, subplotBoard, emotionalArcs, characterMatrix, chapterSummaries, parentCanon, fanficCanon, volumeOutline] =
       await Promise.all([
-        this.readFileSafe(join(bookDir, "story/current_state.md")),
+        readCurrentStateWithFallback(bookDir),
         this.readFileSafe(join(bookDir, "story/particle_ledger.md")),
         this.readFileSafe(join(bookDir, "story/pending_hooks.md")),
         this.readFileSafe(join(bookDir, "story/style_guide.md")),
         this.readFileSafe(join(bookDir, "story/subplot_board.md")),
         this.readFileSafe(join(bookDir, "story/emotional_arcs.md")),
-        this.readFileSafe(join(bookDir, "story/character_matrix.md")),
+        readCharacterContext(bookDir),
         this.readFileSafe(join(bookDir, "story/chapter_summaries.md")),
         this.readFileSafe(join(bookDir, "story/parent_canon.md")),
         this.readFileSafe(join(bookDir, "story/fanfic_canon.md")),
-        this.readFileSafe(join(bookDir, "story/volume_outline.md")),
+        readVolumeMap(bookDir),
       ]);
     const currentState = options?.truthFileOverrides?.currentState ?? diskCurrentState;
     const ledger = options?.truthFileOverrides?.ledger ?? diskLedger;

+ 4 - 3
packages/core/src/agents/planner.ts

@@ -1,6 +1,7 @@
 import { readFile, writeFile, mkdir } from "node:fs/promises";
 import { join } from "node:path";
 import { BaseAgent } from "./base.js";
+import { readStoryFrame, readVolumeMap, readCurrentStateWithFallback } from "../utils/outline-paths.js";
 import type { BookConfig } from "../models/book.js";
 import { parseBookRules } from "../models/book-rules.js";
 import { ChapterIntentSchema, type ChapterConflict, type ChapterIntent } from "../models/input-governance.js";
@@ -58,11 +59,11 @@ export class PlannerAgent extends BaseAgent {
     ] = await Promise.all([
       this.readFileOrDefault(sourcePaths.authorIntent),
       this.readFileOrDefault(sourcePaths.currentFocus),
-      this.readFileOrDefault(sourcePaths.storyBible),
-      this.readFileOrDefault(sourcePaths.volumeOutline),
+      readStoryFrame(input.bookDir),
+      readVolumeMap(input.bookDir),
       this.readFileOrDefault(sourcePaths.chapterSummaries),
       this.readFileOrDefault(sourcePaths.bookRules),
-      this.readFileOrDefault(sourcePaths.currentState),
+      readCurrentStateWithFallback(input.bookDir),
     ]);
 
     const outlineNode = this.findOutlineNode(volumeOutline, input.chapterNumber);

+ 5 - 4
packages/core/src/agents/reviser.ts

@@ -5,6 +5,7 @@ import type { LengthSpec } from "../models/length-governance.js";
 import type { AuditIssue } from "./continuity.js";
 import type { ContextPackage, RuleStack } from "../models/input-governance.js";
 import { readGenreProfile, readBookLanguage, readBookRules } from "./rules-reader.js";
+import { readStoryFrame, readVolumeMap, readCharacterContext, readCurrentStateWithFallback } from "../utils/outline-paths.js";
 import { countChapterLength } from "../utils/length-metrics.js";
 import { buildGovernedMemoryEvidenceBlocks } from "../utils/governed-context.js";
 import { filterSummaries } from "../utils/context-filter.js";
@@ -74,13 +75,13 @@ export class ReviserAgent extends BaseAgent {
     },
   ): Promise<ReviseOutput> {
     const [currentState, ledger, hooks, styleGuideRaw, volumeOutline, storyBible, characterMatrix, chapterSummaries, parentCanon, fanficCanon] = await Promise.all([
-      this.readFileSafe(join(bookDir, "story/current_state.md")),
+      readCurrentStateWithFallback(bookDir),
       this.readFileSafe(join(bookDir, "story/particle_ledger.md")),
       this.readFileSafe(join(bookDir, "story/pending_hooks.md")),
       this.readFileSafe(join(bookDir, "story/style_guide.md")),
-      this.readFileSafe(join(bookDir, "story/volume_outline.md")),
-      this.readFileSafe(join(bookDir, "story/story_bible.md")),
-      this.readFileSafe(join(bookDir, "story/character_matrix.md")),
+      readVolumeMap(bookDir),
+      readStoryFrame(bookDir),
+      readCharacterContext(bookDir),
       this.readFileSafe(join(bookDir, "story/chapter_summaries.md")),
       this.readFileSafe(join(bookDir, "story/parent_canon.md")),
       this.readFileSafe(join(bookDir, "story/fanfic_canon.md")),

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

@@ -8,6 +8,7 @@ import { buildObserverSystemPrompt, buildObserverUserPrompt } from "./observer-p
 import { parseSettlerDeltaOutput } from "./settler-delta-parser.js";
 import { parseSettlementOutput } from "./settler-parser.js";
 import { readGenreProfile, readBookRules } from "./rules-reader.js";
+import { readStoryFrame, readVolumeMap, readCharacterContext, readCurrentStateWithFallback } from "../utils/outline-paths.js";
 import {
   detectCrossChapterRepetition,
   detectParagraphLengthDrift,
@@ -123,16 +124,16 @@ export class WriterAgent extends BaseAgent {
       chapterSummaries, subplotBoard, emotionalArcs, characterMatrix, styleProfileRaw,
       parentCanon, fanficCanonRaw,
     ] = await Promise.all([
-        this.readFileOrDefault(join(bookDir, "story/story_bible.md")),
-        this.readFileOrDefault(join(bookDir, "story/volume_outline.md")),
+        readStoryFrame(bookDir),
+        readVolumeMap(bookDir),
         this.readFileOrDefault(join(bookDir, "story/style_guide.md")),
-        this.readFileOrDefault(join(bookDir, "story/current_state.md")),
+        readCurrentStateWithFallback(bookDir),
         this.readFileOrDefault(join(bookDir, "story/particle_ledger.md")),
         this.readFileOrDefault(join(bookDir, "story/pending_hooks.md")),
         this.readFileOrDefault(join(bookDir, "story/chapter_summaries.md")),
         this.readFileOrDefault(join(bookDir, "story/subplot_board.md")),
         this.readFileOrDefault(join(bookDir, "story/emotional_arcs.md")),
-        this.readFileOrDefault(join(bookDir, "story/character_matrix.md")),
+        readCharacterContext(bookDir),
         this.readFileOrDefault(join(bookDir, "story/style_profile.json")),
         this.readFileOrDefault(join(bookDir, "story/parent_canon.md")),
         this.readFileOrDefault(join(bookDir, "story/fanfic_canon.md")),
@@ -419,14 +420,14 @@ export class WriterAgent extends BaseAgent {
       characterMatrix,
       volumeOutline,
     ] = await Promise.all([
-      this.readFileOrDefault(join(input.bookDir, "story/current_state.md")),
+      readCurrentStateWithFallback(input.bookDir),
       this.readFileOrDefault(join(input.bookDir, "story/particle_ledger.md")),
       this.readFileOrDefault(join(input.bookDir, "story/pending_hooks.md")),
       this.readFileOrDefault(join(input.bookDir, "story/chapter_summaries.md")),
       this.readFileOrDefault(join(input.bookDir, "story/subplot_board.md")),
       this.readFileOrDefault(join(input.bookDir, "story/emotional_arcs.md")),
-      this.readFileOrDefault(join(input.bookDir, "story/character_matrix.md")),
-      this.readFileOrDefault(join(input.bookDir, "story/volume_outline.md")),
+      readCharacterContext(input.bookDir),
+      readVolumeMap(input.bookDir),
     ]);
 
     const { profile: genreProfile } = await readGenreProfile(this.ctx.projectRoot, input.book.genre);

+ 6 - 5
packages/core/src/pipeline/runner.ts

@@ -35,6 +35,7 @@ import { loadNarrativeMemorySeed, loadSnapshotCurrentStateFacts } from "../state
 import { rewriteStructuredStateFromMarkdown } from "../state/state-bootstrap.js";
 import { readFile, readdir, writeFile, mkdir, rename, rm, stat } from "node:fs/promises";
 import { join } from "node:path";
+import { readStoryFrame, readVolumeMap, readCharacterContext, readCurrentStateWithFallback } from "../utils/outline-paths.js";
 import {
   parseStateDegradedReviewNote,
   resolveStateDegradedBaseStatus,
@@ -1119,8 +1120,8 @@ export class PipelineRunner {
         readSafe(join(storyDir, "current_state.md")),
         readSafe(join(storyDir, "particle_ledger.md")),
         readSafe(join(storyDir, "pending_hooks.md")),
-        readSafe(join(storyDir, "story_bible.md")),
-        readSafe(join(storyDir, "volume_outline.md")),
+        readStoryFrame(bookDir, "(文件不存在)"),
+        readVolumeMap(bookDir, "(文件不存在)"),
         readSafe(join(storyDir, "book_rules.md")),
       ]);
 
@@ -1847,14 +1848,14 @@ export class PipelineRunner {
 
     const [storyBible, currentState, ledger, hooks, summaries, subplots, emotions, matrix] =
       await Promise.all([
-        readSafe(join(parentDir, "story/story_bible.md")),
-        readSafe(join(parentDir, "story/current_state.md")),
+        readStoryFrame(parentDir, "(无)"),
+        readCurrentStateWithFallback(parentDir, "(无)"),
         readSafe(join(parentDir, "story/particle_ledger.md")),
         readSafe(join(parentDir, "story/pending_hooks.md")),
         readSafe(join(parentDir, "story/chapter_summaries.md")),
         readSafe(join(parentDir, "story/subplot_board.md")),
         readSafe(join(parentDir, "story/emotional_arcs.md")),
-        readSafe(join(parentDir, "story/character_matrix.md")),
+        readCharacterContext(parentDir, "(无)"),
       ]);
 
     const response = await chatCompletion(this.config.client, this.config.model, [

+ 2 - 1
packages/core/src/utils/memory-retrieval.ts

@@ -1,5 +1,6 @@
 import { readFile } from "node:fs/promises";
 import { join } from "node:path";
+import { readCurrentStateWithFallback } from "./outline-paths.js";
 import {
   ChapterSummariesStateSchema,
   CurrentStateStateSchema,
@@ -71,7 +72,7 @@ export async function retrieveMemorySelection(params: {
     structuredHooks,
     structuredSummaries,
   ] = await Promise.all([
-    readFile(join(storyDir, "current_state.md"), "utf-8").catch(() => ""),
+    readCurrentStateWithFallback(params.bookDir),
     readFile(join(storyDir, "volume_summaries.md"), "utf-8").catch(() => ""),
     readStructuredState(join(stateDir, "current_state.json"), CurrentStateStateSchema),
     readStructuredState(join(stateDir, "hooks.json"), HooksStateSchema),