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

fix: revise not writing to file (#31) + config typo suggestion + docs

Ma 7 месяцев назад
Родитель
Сommit
e0f3bd6bbb
4 измененных файлов с 31 добавлено и 18 удалено
  1. 1 1
      README.md
  2. 16 4
      packages/cli/src/commands/config.ts
  3. 5 5
      packages/cli/src/commands/revise.ts
  4. 9 8
      packages/core/src/pipeline/runner.ts

+ 1 - 1
README.md

@@ -540,7 +540,7 @@ TypeScript 单仓库,pnpm workspaces 管理。
 - [x] 文风仿写(统计指纹 + LLM 风格指南 + 写手注入)
 - [x] 文风仿写(统计指纹 + LLM 风格指南 + 写手注入)
 - [x] 写后验证器(11 条硬规则 + 自动 spot-fix)
 - [x] 写后验证器(11 条硬规则 + 自动 spot-fix)
 - [x] 审计-修订闭环加固(AI 标记守卫 + 温度锁)
 - [x] 审计-修订闭环加固(AI 标记守卫 + 温度锁)
-- [x] 续写接盘(import chapters + 自动逆向真相文件 + 断点续导)
+- [x] 续写已有作品(import chapters + 自动逆向真相文件 + 断点续导)
 - [x] 多 Provider 路由(不同 agent 走不同 API 地址和 Key,`inkos config set-model --base-url --provider`)
 - [x] 多 Provider 路由(不同 agent 走不同 API 地址和 Key,`inkos config set-model --base-url --provider`)
 - [x] 数据分析(`inkos analytics`:审计通过率、高频问题、章节排名)
 - [x] 数据分析(`inkos analytics`:审计通过率、高频问题、章节排名)
 - [ ] `packages/studio` Web UI 审阅编辑界面(Vite + React + Hono)
 - [ ] `packages/studio` Web UI 审阅编辑界面(Vite + React + Hono)

+ 16 - 4
packages/cli/src/commands/config.ts

@@ -30,13 +30,25 @@ configCommand
         "daemon.maxChaptersPerDay",
         "daemon.maxChaptersPerDay",
       ]);
       ]);
       if (!KNOWN_KEYS.has(key)) {
       if (!KNOWN_KEYS.has(key)) {
-        // Find closest match for typo suggestion
+        // Find closest match by edit distance on the last segment
         const candidates = [...KNOWN_KEYS];
         const candidates = [...KNOWN_KEYS];
-        const suggestion = candidates.find(k => {
+        const inputParts = key.split(".");
+        const samePrefixCandidates = candidates.filter(k => {
           const parts = k.split(".");
           const parts = k.split(".");
-          const inputParts = key.split(".");
-          return parts.length === inputParts.length && parts[0] === inputParts[0];
+          return parts.length === inputParts.length && parts.slice(0, -1).join(".") === inputParts.slice(0, -1).join(".");
         });
         });
+        const editDist = (a: string, b: string): number => {
+          const m = a.length, n = b.length;
+          const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
+          for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++)
+            dp[i]![j] = Math.min(dp[i-1]![j]! + 1, dp[i]![j-1]! + 1, dp[i-1]![j-1]! + (a[i-1] !== b[j-1] ? 1 : 0));
+          return dp[m]![n]!;
+        };
+        const inputLast = inputParts[inputParts.length - 1]!;
+        const suggestion = samePrefixCandidates
+          .map(k => ({ k, d: editDist(k.split(".").pop()!, inputLast) }))
+          .sort((a, b) => a.d - b.d)
+          .find(x => x.d <= 3)?.k;
         logError(`Unknown config key "${key}".${suggestion ? ` Did you mean "${suggestion}"?` : ""}`);
         logError(`Unknown config key "${key}".${suggestion ? ` Did you mean "${suggestion}"?` : ""}`);
         log(`Known keys: ${candidates.join(", ")}`);
         log(`Known keys: ${candidates.join(", ")}`);
         process.exit(1);
         process.exit(1);

+ 5 - 5
packages/cli/src/commands/revise.ts

@@ -32,14 +32,14 @@ export const reviseCommand = new Command("revise")
 
 
       if (opts.json) {
       if (opts.json) {
         log(JSON.stringify(result, null, 2));
         log(JSON.stringify(result, null, 2));
+      } else if (result.fixedIssues.length === 0) {
+        log(`  Chapter ${result.chapterNumber}: no issues to fix (audit passed)`);
       } else {
       } else {
         log(`  Chapter ${result.chapterNumber} revised`);
         log(`  Chapter ${result.chapterNumber} revised`);
         log(`  Words: ${result.wordCount}`);
         log(`  Words: ${result.wordCount}`);
-        if (result.fixedIssues.length > 0) {
-          log("  Fixed:");
-          for (const fix of result.fixedIssues) {
-            log(`    - ${fix}`);
-          }
+        log("  Fixed:");
+        for (const fix of result.fixedIssues) {
+          log(`    - ${fix}`);
         }
         }
       }
       }
     } catch (e) {
     } catch (e) {

+ 9 - 8
packages/core/src/pipeline/runner.ts

@@ -317,8 +317,8 @@ export class PipelineRunner {
       const auditor = new ContinuityAuditor(this.agentCtxFor("auditor", bookId));
       const auditor = new ContinuityAuditor(this.agentCtxFor("auditor", bookId));
       const auditResult = await auditor.auditChapter(bookDir, content, targetChapter, book.genre);
       const auditResult = await auditor.auditChapter(bookDir, content, targetChapter, book.genre);
 
 
-      if (auditResult.passed) {
-        return { chapterNumber: targetChapter, wordCount: content.length, fixedIssues: ["No issues to fix"] };
+      if (auditResult.passed && auditResult.issues.filter(i => i.severity === "warning" || i.severity === "critical").length === 0) {
+        return { chapterNumber: targetChapter, wordCount: content.length, fixedIssues: [] };
       }
       }
 
 
       const { profile: gp } = await this.loadGenreProfile(book.genre);
       const { profile: gp } = await this.loadGenreProfile(book.genre);
@@ -337,13 +337,14 @@ export class PipelineRunner {
       const files = await readdir(chaptersDir);
       const files = await readdir(chaptersDir);
       const paddedNum = String(targetChapter).padStart(4, "0");
       const paddedNum = String(targetChapter).padStart(4, "0");
       const existingFile = files.find((f) => f.startsWith(paddedNum) && f.endsWith(".md"));
       const existingFile = files.find((f) => f.startsWith(paddedNum) && f.endsWith(".md"));
-      if (existingFile) {
-        await writeFile(
-          join(chaptersDir, existingFile),
-          `# 第${targetChapter}章 ${chapterMeta.title}\n\n${reviseOutput.revisedContent}`,
-          "utf-8",
-        );
+      if (!existingFile) {
+        throw new Error(`Chapter ${targetChapter} file not found in ${chaptersDir} (expected filename starting with ${paddedNum})`);
       }
       }
+      await writeFile(
+        join(chaptersDir, existingFile),
+        `# 第${targetChapter}章 ${chapterMeta.title}\n\n${reviseOutput.revisedContent}`,
+        "utf-8",
+      );
 
 
       // Update truth files
       // Update truth files
       const storyDir = join(bookDir, "story");
       const storyDir = join(bookDir, "story");