Преглед изворни кода

fix: report resumed imports using processed chapter counts

Ma пре 7 месеци
родитељ
комит
2cd739c128

+ 55 - 0
packages/core/src/__tests__/pipeline-runner.test.ts

@@ -9,6 +9,7 @@ import { ContinuityAuditor, type AuditIssue, type AuditResult } from "../agents/
 import { ReviserAgent, type ReviseOutput } from "../agents/reviser.js";
 import { ChapterAnalyzerAgent } from "../agents/chapter-analyzer.js";
 import type { BookConfig } from "../models/book.js";
+import type { ChapterMeta } from "../models/chapter.js";
 
 const ZERO_USAGE = {
   promptTokens: 0,
@@ -323,4 +324,58 @@ describe("PipelineRunner", () => {
 
     await rm(root, { recursive: true, force: true });
   });
+
+  it("reports only resumed chapters in import results", async () => {
+    const { root, runner, state, bookId } = await createRunnerFixture();
+    const now = "2026-03-19T00:00:00.000Z";
+    const existingIndex: ChapterMeta[] = [
+      {
+        number: 1,
+        title: "One",
+        status: "imported",
+        wordCount: 10,
+        createdAt: now,
+        updatedAt: now,
+        auditIssues: [],
+      },
+      {
+        number: 2,
+        title: "Two",
+        status: "imported",
+        wordCount: 20,
+        createdAt: now,
+        updatedAt: now,
+        auditIssues: [],
+      },
+    ];
+    await state.saveChapterIndex(bookId, existingIndex);
+
+    vi.spyOn(ChapterAnalyzerAgent.prototype, "analyzeChapter").mockImplementation(async (input) =>
+      createAnalyzedOutput({
+        chapterNumber: input.chapterNumber,
+        title: input.chapterTitle ?? `Chapter ${input.chapterNumber}`,
+        content: input.chapterContent,
+        wordCount: input.chapterContent.length,
+      }),
+    );
+    vi.spyOn(WriterAgent.prototype, "saveChapter").mockResolvedValue(undefined);
+    vi.spyOn(WriterAgent.prototype, "saveNewTruthFiles").mockResolvedValue(undefined);
+
+    const result = await runner.importChapters({
+      bookId,
+      resumeFrom: 3,
+      chapters: [
+        { title: "One", content: "1111111111" },
+        { title: "Two", content: "22222222222222222222" },
+        { title: "Three", content: "333333333333333" },
+        { title: "Four", content: "4444444444444444444444444" },
+      ],
+    });
+
+    expect(result.importedCount).toBe(2);
+    expect(result.totalWords).toBe("333333333333333".length + "4444444444444444444444444".length);
+    expect(result.nextChapter).toBe(5);
+
+    await rm(root, { recursive: true, force: true });
+  });
 });

+ 4 - 2
packages/core/src/pipeline/runner.ts

@@ -998,6 +998,7 @@ ${matrix}`,
       const analyzer = new ChapterAnalyzerAgent(this.agentCtxFor("chapter-analyzer", input.bookId));
       const writer = new WriterAgent(this.agentCtxFor("writer", input.bookId));
       let totalWords = 0;
+      let importedCount = 0;
 
       for (let i = startFrom - 1; i < input.chapters.length; i++) {
         const ch = input.chapters[i]!;
@@ -1050,15 +1051,16 @@ ${matrix}`,
         // Snapshot state after each chapter for rollback + resume support
         await this.state.snapshotState(input.bookId, chapterNumber);
 
+        importedCount++;
         totalWords += ch.content.length;
       }
 
       const nextChapter = input.chapters.length + 1;
-      log?.info(`Done. ${input.chapters.length} chapters imported, ${totalWords} chars. Next chapter: ${nextChapter}`);
+      log?.info(`Done. ${importedCount} chapters imported, ${totalWords} chars. Next chapter: ${nextChapter}`);
 
       return {
         bookId: input.bookId,
-        importedCount: input.chapters.length,
+        importedCount,
         totalWords,
         nextChapter,
       };