@actalk/inkos-core 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/agents/architect.d.ts +6 -1
  2. package/dist/agents/architect.d.ts.map +1 -1
  3. package/dist/agents/architect.js +362 -83
  4. package/dist/agents/architect.js.map +1 -1
  5. package/dist/agents/chapter-analyzer.d.ts +6 -0
  6. package/dist/agents/chapter-analyzer.d.ts.map +1 -1
  7. package/dist/agents/chapter-analyzer.js +220 -17
  8. package/dist/agents/chapter-analyzer.js.map +1 -1
  9. package/dist/agents/composer.d.ts +28 -0
  10. package/dist/agents/composer.d.ts.map +1 -0
  11. package/dist/agents/composer.js +154 -0
  12. package/dist/agents/composer.js.map +1 -0
  13. package/dist/agents/consolidator.d.ts.map +1 -1
  14. package/dist/agents/consolidator.js +17 -8
  15. package/dist/agents/consolidator.js.map +1 -1
  16. package/dist/agents/continuity.d.ts +10 -0
  17. package/dist/agents/continuity.d.ts.map +1 -1
  18. package/dist/agents/continuity.js +312 -133
  19. package/dist/agents/continuity.js.map +1 -1
  20. package/dist/agents/en-prompt-sections.d.ts.map +1 -1
  21. package/dist/agents/en-prompt-sections.js +1 -0
  22. package/dist/agents/en-prompt-sections.js.map +1 -1
  23. package/dist/agents/length-normalizer.d.ts +32 -0
  24. package/dist/agents/length-normalizer.d.ts.map +1 -0
  25. package/dist/agents/length-normalizer.js +156 -0
  26. package/dist/agents/length-normalizer.js.map +1 -0
  27. package/dist/agents/planner.d.ts +42 -0
  28. package/dist/agents/planner.d.ts.map +1 -0
  29. package/dist/agents/planner.js +382 -0
  30. package/dist/agents/planner.js.map +1 -0
  31. package/dist/agents/post-write-validator.d.ts +6 -1
  32. package/dist/agents/post-write-validator.d.ts.map +1 -1
  33. package/dist/agents/post-write-validator.js +88 -2
  34. package/dist/agents/post-write-validator.js.map +1 -1
  35. package/dist/agents/reviser.d.ts +10 -1
  36. package/dist/agents/reviser.d.ts.map +1 -1
  37. package/dist/agents/reviser.js +151 -36
  38. package/dist/agents/reviser.js.map +1 -1
  39. package/dist/agents/rules-reader.d.ts +1 -0
  40. package/dist/agents/rules-reader.d.ts.map +1 -1
  41. package/dist/agents/rules-reader.js +13 -0
  42. package/dist/agents/rules-reader.js.map +1 -1
  43. package/dist/agents/settler-delta-parser.d.ts +7 -0
  44. package/dist/agents/settler-delta-parser.d.ts.map +1 -0
  45. package/dist/agents/settler-delta-parser.js +35 -0
  46. package/dist/agents/settler-delta-parser.js.map +1 -0
  47. package/dist/agents/settler-prompts.d.ts +2 -0
  48. package/dist/agents/settler-prompts.d.ts.map +1 -1
  49. package/dist/agents/settler-prompts.js +77 -63
  50. package/dist/agents/settler-prompts.js.map +1 -1
  51. package/dist/agents/writer-parser.d.ts +3 -2
  52. package/dist/agents/writer-parser.d.ts.map +1 -1
  53. package/dist/agents/writer-parser.js +44 -13
  54. package/dist/agents/writer-parser.js.map +1 -1
  55. package/dist/agents/writer-prompts.d.ts +2 -1
  56. package/dist/agents/writer-prompts.d.ts.map +1 -1
  57. package/dist/agents/writer-prompts.js +65 -21
  58. package/dist/agents/writer-prompts.js.map +1 -1
  59. package/dist/agents/writer.d.ts +28 -1
  60. package/dist/agents/writer.d.ts.map +1 -1
  61. package/dist/agents/writer.js +426 -67
  62. package/dist/agents/writer.js.map +1 -1
  63. package/dist/index.d.ts +18 -3
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +17 -2
  66. package/dist/index.js.map +1 -1
  67. package/dist/llm/provider.d.ts +1 -0
  68. package/dist/llm/provider.d.ts.map +1 -1
  69. package/dist/llm/provider.js +19 -6
  70. package/dist/llm/provider.js.map +1 -1
  71. package/dist/models/chapter.d.ts +71 -0
  72. package/dist/models/chapter.d.ts.map +1 -1
  73. package/dist/models/chapter.js +3 -0
  74. package/dist/models/chapter.js.map +1 -1
  75. package/dist/models/input-governance.d.ts +351 -0
  76. package/dist/models/input-governance.d.ts.map +1 -0
  77. package/dist/models/input-governance.js +78 -0
  78. package/dist/models/input-governance.js.map +1 -0
  79. package/dist/models/length-governance.d.ts +93 -0
  80. package/dist/models/length-governance.d.ts.map +1 -0
  81. package/dist/models/length-governance.js +34 -0
  82. package/dist/models/length-governance.js.map +1 -0
  83. package/dist/models/project.d.ts +5 -0
  84. package/dist/models/project.d.ts.map +1 -1
  85. package/dist/models/project.js +2 -0
  86. package/dist/models/project.js.map +1 -1
  87. package/dist/models/runtime-state.d.ts +521 -0
  88. package/dist/models/runtime-state.d.ts.map +1 -0
  89. package/dist/models/runtime-state.js +78 -0
  90. package/dist/models/runtime-state.js.map +1 -0
  91. package/dist/pipeline/agent.d.ts +2 -1
  92. package/dist/pipeline/agent.d.ts.map +1 -1
  93. package/dist/pipeline/agent.js +90 -5
  94. package/dist/pipeline/agent.js.map +1 -1
  95. package/dist/pipeline/runner.d.ts +65 -1
  96. package/dist/pipeline/runner.d.ts.map +1 -1
  97. package/dist/pipeline/runner.js +1029 -73
  98. package/dist/pipeline/runner.js.map +1 -1
  99. package/dist/state/manager.d.ts +14 -0
  100. package/dist/state/manager.d.ts.map +1 -1
  101. package/dist/state/manager.js +114 -0
  102. package/dist/state/manager.js.map +1 -1
  103. package/dist/state/memory-db.d.ts +15 -0
  104. package/dist/state/memory-db.d.ts.map +1 -1
  105. package/dist/state/memory-db.js +119 -10
  106. package/dist/state/memory-db.js.map +1 -1
  107. package/dist/state/runtime-state-store.d.ts +23 -0
  108. package/dist/state/runtime-state-store.d.ts.map +1 -0
  109. package/dist/state/runtime-state-store.js +100 -0
  110. package/dist/state/runtime-state-store.js.map +1 -0
  111. package/dist/state/state-bootstrap.d.ts +19 -0
  112. package/dist/state/state-bootstrap.d.ts.map +1 -0
  113. package/dist/state/state-bootstrap.js +394 -0
  114. package/dist/state/state-bootstrap.js.map +1 -0
  115. package/dist/state/state-projections.d.ts +5 -0
  116. package/dist/state/state-projections.d.ts.map +1 -0
  117. package/dist/state/state-projections.js +164 -0
  118. package/dist/state/state-projections.js.map +1 -0
  119. package/dist/state/state-reducer.d.ts +12 -0
  120. package/dist/state/state-reducer.d.ts.map +1 -0
  121. package/dist/state/state-reducer.js +146 -0
  122. package/dist/state/state-reducer.js.map +1 -0
  123. package/dist/state/state-validator.d.ts +12 -0
  124. package/dist/state/state-validator.d.ts.map +1 -0
  125. package/dist/state/state-validator.js +56 -0
  126. package/dist/state/state-validator.js.map +1 -0
  127. package/dist/utils/chapter-splitter.d.ts +2 -0
  128. package/dist/utils/chapter-splitter.d.ts.map +1 -1
  129. package/dist/utils/chapter-splitter.js +22 -4
  130. package/dist/utils/chapter-splitter.js.map +1 -1
  131. package/dist/utils/config-loader.d.ts +3 -1
  132. package/dist/utils/config-loader.d.ts.map +1 -1
  133. package/dist/utils/config-loader.js +14 -3
  134. package/dist/utils/config-loader.js.map +1 -1
  135. package/dist/utils/context-filter.js +1 -1
  136. package/dist/utils/context-filter.js.map +1 -1
  137. package/dist/utils/governed-context.d.ts +7 -0
  138. package/dist/utils/governed-context.d.ts.map +1 -0
  139. package/dist/utils/governed-context.js +22 -0
  140. package/dist/utils/governed-context.js.map +1 -0
  141. package/dist/utils/governed-working-set.d.ts +18 -0
  142. package/dist/utils/governed-working-set.d.ts.map +1 -0
  143. package/dist/utils/governed-working-set.js +295 -0
  144. package/dist/utils/governed-working-set.js.map +1 -0
  145. package/dist/utils/hook-governance.d.ts +26 -0
  146. package/dist/utils/hook-governance.d.ts.map +1 -0
  147. package/dist/utils/hook-governance.js +128 -0
  148. package/dist/utils/hook-governance.js.map +1 -0
  149. package/dist/utils/hook-health.d.ts +14 -0
  150. package/dist/utils/hook-health.d.ts.map +1 -0
  151. package/dist/utils/hook-health.js +68 -0
  152. package/dist/utils/hook-health.js.map +1 -0
  153. package/dist/utils/length-metrics.d.ts +10 -0
  154. package/dist/utils/length-metrics.d.ts.map +1 -0
  155. package/dist/utils/length-metrics.js +85 -0
  156. package/dist/utils/length-metrics.js.map +1 -0
  157. package/dist/utils/long-span-fatigue.d.ts +28 -0
  158. package/dist/utils/long-span-fatigue.d.ts.map +1 -0
  159. package/dist/utils/long-span-fatigue.js +359 -0
  160. package/dist/utils/long-span-fatigue.js.map +1 -0
  161. package/dist/utils/memory-retrieval.d.ts +39 -0
  162. package/dist/utils/memory-retrieval.d.ts.map +1 -0
  163. package/dist/utils/memory-retrieval.js +574 -0
  164. package/dist/utils/memory-retrieval.js.map +1 -0
  165. package/dist/utils/spot-fix-patches.d.ts +14 -0
  166. package/dist/utils/spot-fix-patches.d.ts.map +1 -0
  167. package/dist/utils/spot-fix-patches.js +75 -0
  168. package/dist/utils/spot-fix-patches.js.map +1 -0
  169. package/package.json +1 -1
@@ -1,57 +1,190 @@
1
1
  import { BaseAgent } from "./base.js";
2
- import { readGenreProfile, readBookRules } from "./rules-reader.js";
3
- import { getFanficDimensionConfig } from "./fanfic-dimensions.js";
2
+ import { readGenreProfile, readBookLanguage, readBookRules } from "./rules-reader.js";
3
+ import { getFanficDimensionConfig, FANFIC_DIMENSIONS } from "./fanfic-dimensions.js";
4
4
  import { readFile, readdir } from "node:fs/promises";
5
5
  import { filterHooks, filterSummaries, filterSubplots, filterEmotionalArcs, filterCharacterMatrix } from "../utils/context-filter.js";
6
+ import { buildGovernedMemoryEvidenceBlocks } from "../utils/governed-context.js";
6
7
  import { join } from "node:path";
7
- // Dimension ID → name mapping
8
- const DIMENSION_MAP = {
9
- 1: "OOC检查",
10
- 2: "时间线检查",
11
- 3: "设定冲突",
12
- 4: "战力崩坏",
13
- 5: "数值检查",
14
- 6: "伏笔检查",
15
- 7: "节奏检查",
16
- 8: "文风检查",
17
- 9: "信息越界",
18
- 10: "词汇疲劳",
19
- 11: "利益链断裂",
20
- 12: "年代考据",
21
- 13: "配角降智",
22
- 14: "配角工具人化",
23
- 15: "爽点虚化",
24
- 16: "台词失真",
25
- 17: "流水账",
26
- 18: "知识库污染",
27
- 19: "视角一致性",
28
- 20: "段落等长",
29
- 21: "套话密度",
30
- 22: "公式化转折",
31
- 23: "列表式结构",
32
- 24: "支线停滞",
33
- 25: "弧线平坦",
34
- 26: "节奏单调",
35
- 27: "敏感词检查",
36
- 28: "正传事件冲突",
37
- 29: "未来信息泄露",
38
- 30: "世界规则跨书一致性",
39
- 31: "番外伏笔隔离",
40
- 32: "读者期待管理",
41
- 33: "大纲偏离检测",
42
- 34: "角色还原度",
43
- 35: "世界规则遵守",
44
- 36: "关系动态",
45
- 37: "正典事件一致性",
8
+ const DIMENSION_LABELS = {
9
+ 1: { zh: "OOC检查", en: "OOC Check" },
10
+ 2: { zh: "时间线检查", en: "Timeline Check" },
11
+ 3: { zh: "设定冲突", en: "Lore Conflict Check" },
12
+ 4: { zh: "战力崩坏", en: "Power Scaling Check" },
13
+ 5: { zh: "数值检查", en: "Numerical Consistency Check" },
14
+ 6: { zh: "伏笔检查", en: "Hook Check" },
15
+ 7: { zh: "节奏检查", en: "Pacing Check" },
16
+ 8: { zh: "文风检查", en: "Style Check" },
17
+ 9: { zh: "信息越界", en: "Information Boundary Check" },
18
+ 10: { zh: "词汇疲劳", en: "Lexical Fatigue Check" },
19
+ 11: { zh: "利益链断裂", en: "Incentive Chain Check" },
20
+ 12: { zh: "年代考据", en: "Era Accuracy Check" },
21
+ 13: { zh: "配角降智", en: "Side Character Competence Check" },
22
+ 14: { zh: "配角工具人化", en: "Side Character Instrumentalization Check" },
23
+ 15: { zh: "爽点虚化", en: "Payoff Dilution Check" },
24
+ 16: { zh: "台词失真", en: "Dialogue Authenticity Check" },
25
+ 17: { zh: "流水账", en: "Chronicle Drift Check" },
26
+ 18: { zh: "知识库污染", en: "Knowledge Base Pollution Check" },
27
+ 19: { zh: "视角一致性", en: "POV Consistency Check" },
28
+ 20: { zh: "段落等长", en: "Paragraph Uniformity Check" },
29
+ 21: { zh: "套话密度", en: "Cliche Density Check" },
30
+ 22: { zh: "公式化转折", en: "Formulaic Twist Check" },
31
+ 23: { zh: "列表式结构", en: "List-like Structure Check" },
32
+ 24: { zh: "支线停滞", en: "Subplot Stagnation Check" },
33
+ 25: { zh: "弧线平坦", en: "Arc Flatline Check" },
34
+ 26: { zh: "节奏单调", en: "Pacing Monotony Check" },
35
+ 27: { zh: "敏感词检查", en: "Sensitive Content Check" },
36
+ 28: { zh: "正传事件冲突", en: "Mainline Canon Event Conflict" },
37
+ 29: { zh: "未来信息泄露", en: "Future Knowledge Leak Check" },
38
+ 30: { zh: "世界规则跨书一致性", en: "Cross-Book World Rule Check" },
39
+ 31: { zh: "番外伏笔隔离", en: "Spinoff Hook Isolation Check" },
40
+ 32: { zh: "读者期待管理", en: "Reader Expectation Check" },
41
+ 33: { zh: "大纲偏离检测", en: "Outline Drift Check" },
42
+ 34: { zh: "角色还原度", en: "Character Fidelity Check" },
43
+ 35: { zh: "世界规则遵守", en: "World Rule Compliance Check" },
44
+ 36: { zh: "关系动态", en: "Relationship Dynamics Check" },
45
+ 37: { zh: "正典事件一致性", en: "Canon Event Consistency Check" },
46
46
  };
47
- function buildDimensionList(gp, bookRules, hasParentCanon = false, fanficMode) {
47
+ function containsChinese(text) {
48
+ return /[\u4e00-\u9fff]/u.test(text);
49
+ }
50
+ function resolveGenreLabel(genreId, profileName, language) {
51
+ if (language === "zh" || !containsChinese(profileName)) {
52
+ return profileName;
53
+ }
54
+ if (genreId === "other") {
55
+ return "general";
56
+ }
57
+ return genreId.replace(/[_-]+/g, " ");
58
+ }
59
+ function dimensionName(id, language) {
60
+ return DIMENSION_LABELS[id]?.[language];
61
+ }
62
+ function joinLocalized(items, language) {
63
+ return items.join(language === "en" ? ", " : "、");
64
+ }
65
+ function formatFanficSeverityNote(severity, language) {
66
+ if (language === "en") {
67
+ return severity === "critical"
68
+ ? "Strict check."
69
+ : severity === "info"
70
+ ? "Log only; do not fail the chapter."
71
+ : "Warning level.";
72
+ }
73
+ return severity === "critical"
74
+ ? "(严格检查)"
75
+ : severity === "info"
76
+ ? "(仅记录,不判定失败)"
77
+ : "(警告级别)";
78
+ }
79
+ function buildDimensionNote(id, language, gp, bookRules, fanficMode, fanficConfig) {
80
+ const words = bookRules?.fatigueWordsOverride && bookRules.fatigueWordsOverride.length > 0
81
+ ? bookRules.fatigueWordsOverride
82
+ : gp.fatigueWords;
83
+ if (fanficConfig?.notes.has(id) && language === "zh") {
84
+ return fanficConfig.notes.get(id);
85
+ }
86
+ if (id === 1 && fanficMode === "ooc") {
87
+ return language === "en"
88
+ ? "In OOC mode, personality drift can be intentional; record only, do not fail. Evaluate against the character dossiers in fanfic_canon.md."
89
+ : "OOC模式下角色可偏离性格底色,此维度仅记录不判定失败。参照 fanfic_canon.md 角色档案评估偏离程度。";
90
+ }
91
+ if (id === 1 && fanficMode === "canon") {
92
+ return language === "en"
93
+ ? "Canon-faithful fanfic: characters must stay close to their original personality core. Evaluate against fanfic_canon.md character dossiers."
94
+ : "原作向同人:角色必须严格遵守性格底色。参照 fanfic_canon.md 角色档案中的性格底色和行为模式。";
95
+ }
96
+ if (id === 10 && words.length > 0) {
97
+ return language === "en"
98
+ ? `Fatigue words: ${words.join(", ")}. Also check AI tell markers (仿佛/不禁/宛如/竟然/忽然/猛地); warn when any appears more than once per 3,000 words.`
99
+ : `高疲劳词:${words.join("、")}。同时检查AI标记词(仿佛/不禁/宛如/竟然/忽然/猛地)密度,每3000字超过1次即warning`;
100
+ }
101
+ if (id === 15 && gp.satisfactionTypes.length > 0) {
102
+ return language === "en"
103
+ ? `Payoff types: ${gp.satisfactionTypes.join(", ")}`
104
+ : `爽点类型:${gp.satisfactionTypes.join("、")}`;
105
+ }
106
+ if (id === 12 && bookRules?.eraConstraints) {
107
+ const era = bookRules.eraConstraints;
108
+ const parts = [era.period, era.region].filter(Boolean);
109
+ if (parts.length > 0) {
110
+ return language === "en"
111
+ ? `Era: ${parts.join(", ")}`
112
+ : `年代:${parts.join(",")}`;
113
+ }
114
+ }
115
+ switch (id) {
116
+ case 19:
117
+ return language === "en"
118
+ ? "Check whether POV shifts are signaled clearly and stay consistent with the configured viewpoint."
119
+ : "检查视角切换是否有过渡、是否与设定视角一致";
120
+ case 24:
121
+ return language === "en"
122
+ ? "Cross-check subplot_board and chapter_summaries: if any subplot goes unmentioned or unadvanced for more than 5 chapters -> warning. If subplots exist but none move in the last 3 chapters -> warning."
123
+ : "对照 subplot_board 和 chapter_summaries:如果任何支线超过5章未被提及或推进→warning。如果存在支线但近3章完全没有任何支线推进→warning";
124
+ case 25:
125
+ return language === "en"
126
+ ? "Cross-check emotional_arcs and chapter_summaries: if a major character shows no emotional change for 3 straight chapters (no new pressure, release, or turn) -> warning. Distinguish unchanged circumstances from unchanged inner movement."
127
+ : "对照 emotional_arcs 和 chapter_summaries:如果主要角色连续3章情绪状态无变化(没有新的压力、释放、转变)→warning。注意区分'角色处境未变'和'角色内心未变'";
128
+ case 26:
129
+ return language === "en"
130
+ ? "Cross-check chapter_summaries for chapter-type distribution: 3+ consecutive chapters of the same type -> warning. No payoff or climax chapter for 5+ chapters -> warning. Explicitly list the recent type sequence."
131
+ : "对照 chapter_summaries 的章节类型分布:连续≥3章相同类型(如连续3个事件章/战斗章/布局章)→warning。≥5章没有出现回收章或高潮章→warning。请明确列出最近章节的类型序列";
132
+ case 28:
133
+ return language === "en"
134
+ ? "Check whether spinoff events contradict the mainline canon constraints."
135
+ : "检查番外事件是否与正典约束表矛盾";
136
+ case 29:
137
+ return language === "en"
138
+ ? "Check whether characters reference information that should only be revealed after the divergence point (see the information-boundary table)."
139
+ : "检查角色是否引用了分歧点之后才揭示的信息(参照信息边界表)";
140
+ case 30:
141
+ return language === "en"
142
+ ? "Check whether the spinoff violates mainline world rules (power system, geography, factions)."
143
+ : "检查番外是否违反正传世界规则(力量体系、地理、阵营)";
144
+ case 31:
145
+ return language === "en"
146
+ ? "Check whether the spinoff resolves mainline hooks without authorization (warning level)."
147
+ : "检查番外是否越权回收正传伏笔(warning级别)";
148
+ case 32:
149
+ return language === "en"
150
+ ? "Check: does the chapter ending provide a hook? Has there been a meaningful payoff within the last 3-5 chapters? Is emotional pressure being suppressed for more than 3 chapters without release? Are reader expectation gaps accumulating or being satisfied?"
151
+ : "检查:章尾是否有钩子?最近3-5章内是否有爽点落地?是否存在超过3章的情绪压制无释放?读者的情绪缺口是否在积累或被满足?";
152
+ case 33:
153
+ return language === "en"
154
+ ? "Cross-check volume_outline: does this chapter match the planned beat for the current chapter range? Did it skip planned nodes or consume later nodes too early? Does actual pacing match the planned chapter span? If a beat planned for N chapters is consumed in 1-2 chapters -> critical."
155
+ : "对照 volume_outline:本章内容是否对应卷纲中当前章节范围的剧情节点?是否跳过了节点或提前消耗了后续节点?剧情推进速度是否与卷纲规划的章节跨度匹配?如果卷纲规划某段剧情跨N章但实际1-2章就讲完→critical";
156
+ case 34:
157
+ case 35:
158
+ case 36:
159
+ case 37: {
160
+ if (!fanficConfig)
161
+ return "";
162
+ const severity = fanficConfig.severityOverrides.get(id) ?? "warning";
163
+ const baseNote = language === "en"
164
+ ? {
165
+ 34: "Check whether dialogue tics, speaking style, and behavior remain consistent with the character dossiers in fanfic_canon.md. Deviations need clear situational motivation.",
166
+ 35: "Check whether the chapter violates world rules documented in fanfic_canon.md (geography, power system, faction relations).",
167
+ 36: "Check whether relationship beats remain plausible and aligned with, or meaningfully develop from, the key relationships documented in fanfic_canon.md.",
168
+ 37: "Check whether the chapter contradicts the key event timeline in fanfic_canon.md.",
169
+ }[id]
170
+ : FANFIC_DIMENSIONS.find((dimension) => dimension.id === id)?.baseNote;
171
+ return baseNote
172
+ ? `${baseNote} ${formatFanficSeverityNote(severity, language)}`
173
+ : "";
174
+ }
175
+ default:
176
+ return "";
177
+ }
178
+ }
179
+ function buildDimensionList(gp, bookRules, language, hasParentCanon = false, fanficMode) {
48
180
  const activeIds = new Set(gp.auditDimensions);
49
181
  // Add book-level additional dimensions (supports both numeric IDs and name strings)
50
182
  if (bookRules?.additionalAuditDimensions) {
51
183
  // Build reverse lookup: name → id
52
184
  const nameToId = new Map();
53
- for (const [id, name] of Object.entries(DIMENSION_MAP)) {
54
- nameToId.set(name, Number(id));
185
+ for (const [id, labels] of Object.entries(DIMENSION_LABELS)) {
186
+ nameToId.set(labels.zh, Number(id));
187
+ nameToId.set(labels.en, Number(id));
55
188
  }
56
189
  for (const d of bookRules.additionalAuditDimensions) {
57
190
  if (typeof d === "number") {
@@ -102,59 +235,10 @@ function buildDimensionList(gp, bookRules, hasParentCanon = false, fanficMode) {
102
235
  }
103
236
  const dims = [];
104
237
  for (const id of [...activeIds].sort((a, b) => a - b)) {
105
- const name = DIMENSION_MAP[id];
238
+ const name = dimensionName(id, language);
106
239
  if (!name)
107
240
  continue;
108
- let note = "";
109
- if (id === 10 && gp.fatigueWords.length > 0) {
110
- const words = bookRules?.fatigueWordsOverride && bookRules.fatigueWordsOverride.length > 0
111
- ? bookRules.fatigueWordsOverride
112
- : gp.fatigueWords;
113
- note = `高疲劳词:${words.join("、")}。同时检查AI标记词(仿佛/不禁/宛如/竟然/忽然/猛地)密度,每3000字超过1次即warning`;
114
- }
115
- if (id === 15 && gp.satisfactionTypes.length > 0) {
116
- note = `爽点类型:${gp.satisfactionTypes.join("、")}`;
117
- }
118
- if (id === 12 && bookRules?.eraConstraints) {
119
- const era = bookRules.eraConstraints;
120
- const parts = [era.period, era.region].filter(Boolean);
121
- if (parts.length > 0)
122
- note = `年代:${parts.join(",")}`;
123
- }
124
- if (id === 19) {
125
- note = "检查视角切换是否有过渡、是否与设定视角一致";
126
- }
127
- if (id === 24) {
128
- note = "对照 subplot_board 和 chapter_summaries:如果任何支线超过5章未被提及或推进→warning。如果存在支线但近3章完全没有任何支线推进→warning";
129
- }
130
- if (id === 25) {
131
- note = "对照 emotional_arcs 和 chapter_summaries:如果主要角色连续3章情绪状态无变化(没有新的压力、释放、转变)→warning。注意区分'角色处境未变'和'角色内心未变'";
132
- }
133
- if (id === 26) {
134
- note = "对照 chapter_summaries 的章节类型分布:连续≥3章相同类型(如连续3个事件章/战斗章/布局章)→warning。≥5章没有出现回收章或高潮章→warning。请明确列出最近章节的类型序列";
135
- }
136
- if (id === 28) {
137
- note = "检查番外事件是否与正典约束表矛盾";
138
- }
139
- if (id === 29) {
140
- note = "检查角色是否引用了分歧点之后才揭示的信息(参照信息边界表)";
141
- }
142
- if (id === 30) {
143
- note = "检查番外是否违反正传世界规则(力量体系、地理、阵营)";
144
- }
145
- if (id === 31) {
146
- note = "检查番外是否越权回收正传伏笔(warning级别)";
147
- }
148
- if (id === 32) {
149
- note = "检查:章尾是否有钩子?最近3-5章内是否有爽点落地?是否存在超过3章的情绪压制无释放?读者的情绪缺口是否在积累或被满足?";
150
- }
151
- if (id === 33) {
152
- note = "对照 volume_outline:本章内容是否对应卷纲中当前章节范围的剧情节点?是否跳过了节点或提前消耗了后续节点?剧情推进速度是否与卷纲规划的章节跨度匹配?如果卷纲规划某段剧情跨N章但实际1-2章就讲完→critical";
153
- }
154
- // Fanfic dimension notes (34-37) — mode-aware
155
- if (fanficConfig?.notes.has(id)) {
156
- note = fanficConfig.notes.get(id);
157
- }
241
+ const note = buildDimensionNote(id, language, gp, bookRules, fanficMode, fanficConfig);
158
242
  dims.push({ id, name, note });
159
243
  }
160
244
  return dims;
@@ -164,7 +248,7 @@ export class ContinuityAuditor extends BaseAgent {
164
248
  return "continuity-auditor";
165
249
  }
166
250
  async auditChapter(bookDir, chapterContent, chapterNumber, genre, options) {
167
- const [currentState, ledger, hooks, styleGuideRaw, subplotBoard, emotionalArcs, characterMatrix, chapterSummaries, parentCanon, fanficCanon, volumeOutline] = await Promise.all([
251
+ const [diskCurrentState, diskLedger, diskHooks, styleGuideRaw, subplotBoard, emotionalArcs, characterMatrix, chapterSummaries, parentCanon, fanficCanon, volumeOutline] = await Promise.all([
168
252
  this.readFileSafe(join(bookDir, "story/current_state.md")),
169
253
  this.readFileSafe(join(bookDir, "story/particle_ledger.md")),
170
254
  this.readFileSafe(join(bookDir, "story/pending_hooks.md")),
@@ -177,34 +261,45 @@ export class ContinuityAuditor extends BaseAgent {
177
261
  this.readFileSafe(join(bookDir, "story/fanfic_canon.md")),
178
262
  this.readFileSafe(join(bookDir, "story/volume_outline.md")),
179
263
  ]);
264
+ const currentState = options?.truthFileOverrides?.currentState ?? diskCurrentState;
265
+ const ledger = options?.truthFileOverrides?.ledger ?? diskLedger;
266
+ const hooks = options?.truthFileOverrides?.hooks ?? diskHooks;
180
267
  const hasParentCanon = parentCanon !== "(文件不存在)";
181
268
  const hasFanficCanon = fanficCanon !== "(文件不存在)";
182
269
  // Load last chapter full text for fine-grained continuity checking
183
270
  const previousChapter = await this.loadPreviousChapter(bookDir, chapterNumber);
184
271
  // Load genre profile and book rules
185
272
  const genreId = genre ?? "other";
186
- const { profile: gp } = await readGenreProfile(this.ctx.projectRoot, genreId);
273
+ const [{ profile: gp }, bookLanguage] = await Promise.all([
274
+ readGenreProfile(this.ctx.projectRoot, genreId),
275
+ readBookLanguage(bookDir),
276
+ ]);
187
277
  const parsedRules = await readBookRules(bookDir);
188
278
  const bookRules = parsedRules?.rules ?? null;
189
279
  // Fallback: use book_rules body when style_guide.md doesn't exist
190
280
  const styleGuide = styleGuideRaw !== "(文件不存在)"
191
281
  ? styleGuideRaw
192
282
  : (parsedRules?.body ?? "(无文风指南)");
283
+ const resolvedLanguage = bookLanguage ?? gp.language;
284
+ const isEnglish = resolvedLanguage === "en";
193
285
  const fanficMode = hasFanficCanon ? bookRules?.fanficMode : undefined;
194
- const dimensions = buildDimensionList(gp, bookRules, hasParentCanon, fanficMode);
286
+ const dimensions = buildDimensionList(gp, bookRules, resolvedLanguage, hasParentCanon, fanficMode);
195
287
  const dimList = dimensions
196
- .map((d) => `${d.id}. ${d.name}${d.note ? `(${d.note})` : ""}`)
288
+ .map((d) => `${d.id}. ${d.name}${d.note ? (isEnglish ? ` (${d.note})` : `(${d.note})`) : ""}`)
197
289
  .join("\n");
290
+ const genreLabel = resolveGenreLabel(genreId, gp.name, resolvedLanguage);
198
291
  const protagonistBlock = bookRules?.protagonist
199
- ? `\n主角人设锁定:${bookRules.protagonist.name},${bookRules.protagonist.personalityLock.join("、")},行为约束:${bookRules.protagonist.behavioralConstraints.join("、")}`
292
+ ? isEnglish
293
+ ? `\n\nProtagonist lock: ${bookRules.protagonist.name}; personality locks: ${joinLocalized(bookRules.protagonist.personalityLock, resolvedLanguage)}; behavioral constraints: ${joinLocalized(bookRules.protagonist.behavioralConstraints, resolvedLanguage)}.`
294
+ : `\n主角人设锁定:${bookRules.protagonist.name},${bookRules.protagonist.personalityLock.join("、")},行为约束:${bookRules.protagonist.behavioralConstraints.join("、")}`
200
295
  : "";
201
296
  const searchNote = gp.eraResearch
202
- ? "\n\n你有联网搜索能力(search_web / fetch_url)。对于涉及真实年代、人物、事件、地理、政策的内容,你必须用search_web核实,不可凭记忆判断。至少对比2个来源交叉验证。"
297
+ ? isEnglish
298
+ ? "\n\nYou have web-search capability (search_web / fetch_url). For real-world eras, people, events, geography, or policies, you must verify with search_web instead of relying on memory. Cross-check at least 2 sources."
299
+ : "\n\n你有联网搜索能力(search_web / fetch_url)。对于涉及真实年代、人物、事件、地理、政策的内容,你必须用search_web核实,不可凭记忆判断。至少对比2个来源交叉验证。"
203
300
  : "";
204
- const resolvedLanguage = gp.language;
205
- const isEnglish = resolvedLanguage === "en";
206
301
  const systemPrompt = isEnglish
207
- ? `You are a strict ${gp.name} web fiction editor. Audit the chapter for continuity, consistency, and quality. ALL OUTPUT MUST BE IN ENGLISH.${protagonistBlock}${searchNote}
302
+ ? `You are a strict ${genreLabel} web fiction editor. Audit the chapter for continuity, consistency, and quality. ALL OUTPUT MUST BE IN ENGLISH.${protagonistBlock}${searchNote}
208
303
 
209
304
  Audit dimensions:
210
305
  ${dimList}
@@ -245,7 +340,9 @@ ${dimList}
245
340
 
246
341
  只有当存在 critical 级别问题时,passed 才为 false。`;
247
342
  const ledgerBlock = gp.numericalSystem
248
- ? `\n## 资源账本\n${ledger}`
343
+ ? isEnglish
344
+ ? `\n## Resource Ledger\n${ledger}`
345
+ : `\n## 资源账本\n${ledger}`
249
346
  : "";
250
347
  // Smart context filtering for auditor — same logic as writer
251
348
  const bookRulesForFilter = parsedRules?.rules ?? null;
@@ -254,40 +351,81 @@ ${dimList}
254
351
  const filteredMatrix = filterCharacterMatrix(characterMatrix, volumeOutline, bookRulesForFilter?.protagonist?.name);
255
352
  const filteredSummaries = filterSummaries(chapterSummaries, chapterNumber);
256
353
  const filteredHooks = filterHooks(hooks);
354
+ const governedMemoryBlocks = options?.contextPackage
355
+ ? buildGovernedMemoryEvidenceBlocks(options.contextPackage, resolvedLanguage)
356
+ : undefined;
357
+ const hooksBlock = governedMemoryBlocks?.hooksBlock
358
+ ?? (filteredHooks !== "(文件不存在)"
359
+ ? isEnglish
360
+ ? `\n## Pending Hooks\n${filteredHooks}\n`
361
+ : `\n## 伏笔池\n${filteredHooks}\n`
362
+ : "");
257
363
  const subplotBlock = filteredSubplots !== "(文件不存在)"
258
- ? `\n## 支线进度板\n${filteredSubplots}\n`
364
+ ? isEnglish
365
+ ? `\n## Subplot Board\n${filteredSubplots}\n`
366
+ : `\n## 支线进度板\n${filteredSubplots}\n`
259
367
  : "";
260
368
  const emotionalBlock = filteredArcs !== "(文件不存在)"
261
- ? `\n## 情感弧线\n${filteredArcs}\n`
369
+ ? isEnglish
370
+ ? `\n## Emotional Arcs\n${filteredArcs}\n`
371
+ : `\n## 情感弧线\n${filteredArcs}\n`
262
372
  : "";
263
373
  const matrixBlock = filteredMatrix !== "(文件不存在)"
264
- ? `\n## 角色交互矩阵\n${filteredMatrix}\n`
265
- : "";
266
- const summariesBlock = filteredSummaries !== "(文件不存在)"
267
- ? `\n## 章节摘要(用于节奏检查)\n${filteredSummaries}\n`
374
+ ? isEnglish
375
+ ? `\n## Character Interaction Matrix\n${filteredMatrix}\n`
376
+ : `\n## 角色交互矩阵\n${filteredMatrix}\n`
268
377
  : "";
378
+ const summariesBlock = governedMemoryBlocks?.summariesBlock
379
+ ?? (filteredSummaries !== "(文件不存在)"
380
+ ? isEnglish
381
+ ? `\n## Chapter Summaries (for pacing checks)\n${filteredSummaries}\n`
382
+ : `\n## 章节摘要(用于节奏检查)\n${filteredSummaries}\n`
383
+ : "");
384
+ const volumeSummariesBlock = governedMemoryBlocks?.volumeSummariesBlock ?? "";
269
385
  const canonBlock = hasParentCanon
270
- ? `\n## 正传正典参照(番外审查专用)\n${parentCanon}\n`
386
+ ? isEnglish
387
+ ? `\n## Mainline Canon Reference (for spinoff audit)\n${parentCanon}\n`
388
+ : `\n## 正传正典参照(番外审查专用)\n${parentCanon}\n`
271
389
  : "";
272
390
  const fanficCanonBlock = hasFanficCanon
273
- ? `\n## 同人正典参照(同人审查专用)\n${fanficCanon}\n`
391
+ ? isEnglish
392
+ ? `\n## Fanfic Canon Reference (for fanfic audit)\n${fanficCanon}\n`
393
+ : `\n## 同人正典参照(同人审查专用)\n${fanficCanon}\n`
274
394
  : "";
275
395
  const outlineBlock = volumeOutline !== "(文件不存在)"
276
- ? `\n## 卷纲(用于大纲偏离检测)\n${volumeOutline}\n`
396
+ ? isEnglish
397
+ ? `\n## Volume Outline (for outline drift checks)\n${volumeOutline}\n`
398
+ : `\n## 卷纲(用于大纲偏离检测)\n${volumeOutline}\n`
399
+ : "";
400
+ const reducedControlBlock = options?.chapterIntent && options.contextPackage && options.ruleStack
401
+ ? this.buildReducedControlBlock(options.chapterIntent, options.contextPackage, options.ruleStack, resolvedLanguage)
402
+ : "";
403
+ const styleGuideBlock = reducedControlBlock.length === 0
404
+ ? isEnglish
405
+ ? `\n## Style Guide\n${styleGuide}`
406
+ : `\n## 文风指南\n${styleGuide}`
277
407
  : "";
278
408
  const prevChapterBlock = previousChapter
279
- ? `\n## 上一章全文(用于衔接检查)\n${previousChapter}\n`
409
+ ? isEnglish
410
+ ? `\n## Previous Chapter Full Text (for transition checks)\n${previousChapter}\n`
411
+ : `\n## 上一章全文(用于衔接检查)\n${previousChapter}\n`
280
412
  : "";
281
- const userPrompt = `请审查第${chapterNumber}章。
413
+ const userPrompt = isEnglish
414
+ ? `Review chapter ${chapterNumber}.
415
+
416
+ ## Current State Card
417
+ ${currentState}
418
+ ${ledgerBlock}
419
+ ${hooksBlock}${volumeSummariesBlock}${subplotBlock}${emotionalBlock}${matrixBlock}${summariesBlock}${canonBlock}${fanficCanonBlock}${reducedControlBlock || outlineBlock}${prevChapterBlock}${styleGuideBlock}
420
+
421
+ ## Chapter Content Under Review
422
+ ${chapterContent}`
423
+ : `请审查第${chapterNumber}章。
282
424
 
283
425
  ## 当前状态卡
284
426
  ${currentState}
285
427
  ${ledgerBlock}
286
- ## 伏笔池
287
- ${filteredHooks}
288
- ${subplotBlock}${emotionalBlock}${matrixBlock}${summariesBlock}${canonBlock}${fanficCanonBlock}${outlineBlock}${prevChapterBlock}
289
- ## 文风指南
290
- ${styleGuide}
428
+ ${hooksBlock}${volumeSummariesBlock}${subplotBlock}${emotionalBlock}${matrixBlock}${summariesBlock}${canonBlock}${fanficCanonBlock}${reducedControlBlock || outlineBlock}${prevChapterBlock}${styleGuideBlock}
291
429
 
292
430
  ## 待审章节内容
293
431
  ${chapterContent}`;
@@ -300,29 +438,29 @@ ${chapterContent}`;
300
438
  const response = gp.eraResearch
301
439
  ? await this.chatWithSearch(chatMessages, chatOptions)
302
440
  : await this.chat(chatMessages, chatOptions);
303
- const result = this.parseAuditResult(response.content);
441
+ const result = this.parseAuditResult(response.content, resolvedLanguage);
304
442
  return { ...result, tokenUsage: response.usage };
305
443
  }
306
- parseAuditResult(content) {
444
+ parseAuditResult(content, language) {
307
445
  // Try multiple JSON extraction strategies (handles small/local models)
308
446
  // Strategy 1: Find balanced JSON object (not greedy)
309
447
  const balanced = this.extractBalancedJson(content);
310
448
  if (balanced) {
311
- const result = this.tryParseAuditJson(balanced);
449
+ const result = this.tryParseAuditJson(balanced, language);
312
450
  if (result)
313
451
  return result;
314
452
  }
315
453
  // Strategy 2: Try the whole content as JSON (some models output pure JSON)
316
454
  const trimmed = content.trim();
317
455
  if (trimmed.startsWith("{")) {
318
- const result = this.tryParseAuditJson(trimmed);
456
+ const result = this.tryParseAuditJson(trimmed, language);
319
457
  if (result)
320
458
  return result;
321
459
  }
322
460
  // Strategy 3: Look for ```json code blocks
323
461
  const codeBlockMatch = content.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
324
462
  if (codeBlockMatch) {
325
- const result = this.tryParseAuditJson(codeBlockMatch[1].trim());
463
+ const result = this.tryParseAuditJson(codeBlockMatch[1].trim(), language);
326
464
  if (result)
327
465
  return result;
328
466
  }
@@ -341,7 +479,7 @@ ${chapterContent}`;
341
479
  const issue = JSON.parse(match[0]);
342
480
  issues.push({
343
481
  severity: issue.severity ?? "warning",
344
- category: issue.category ?? "未分类",
482
+ category: issue.category ?? (language === "en" ? "Uncategorized" : "未分类"),
345
483
  description: issue.description ?? "",
346
484
  suggestion: issue.suggestion ?? "",
347
485
  });
@@ -361,13 +499,54 @@ ${chapterContent}`;
361
499
  passed: false,
362
500
  issues: [{
363
501
  severity: "critical",
364
- category: "系统错误",
365
- description: "审稿输出格式异常,无法解析为 JSON",
366
- suggestion: "可能是模型不支持结构化输出。尝试换一个更大的模型,或检查 API 返回格式。",
502
+ category: language === "en" ? "System Error" : "系统错误",
503
+ description: language === "en"
504
+ ? "Audit output format was invalid and could not be parsed as JSON."
505
+ : "审稿输出格式异常,无法解析为 JSON",
506
+ suggestion: language === "en"
507
+ ? "The model may not support reliable structured output. Try a stronger model or inspect the API response format."
508
+ : "可能是模型不支持结构化输出。尝试换一个更大的模型,或检查 API 返回格式。",
367
509
  }],
368
- summary: "审稿输出解析失败",
510
+ summary: language === "en" ? "Audit output parsing failed" : "审稿输出解析失败",
369
511
  };
370
512
  }
513
+ buildReducedControlBlock(chapterIntent, contextPackage, ruleStack, language) {
514
+ const selectedContext = contextPackage.selectedContext
515
+ .map((entry) => `- ${entry.source}: ${entry.reason}${entry.excerpt ? ` | ${entry.excerpt}` : ""}`)
516
+ .join("\n");
517
+ const overrides = ruleStack.activeOverrides.length > 0
518
+ ? ruleStack.activeOverrides
519
+ .map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
520
+ .join("\n")
521
+ : "- none";
522
+ return language === "en"
523
+ ? `\n## Chapter Control Inputs (compiled by Planner/Composer)
524
+ ${chapterIntent}
525
+
526
+ ### Selected Context
527
+ ${selectedContext || "- none"}
528
+
529
+ ### Rule Stack
530
+ - Hard guardrails: ${ruleStack.sections.hard.join(", ") || "(none)"}
531
+ - Soft constraints: ${ruleStack.sections.soft.join(", ") || "(none)"}
532
+ - Diagnostic rules: ${ruleStack.sections.diagnostic.join(", ") || "(none)"}
533
+
534
+ ### Active Overrides
535
+ ${overrides}\n`
536
+ : `\n## 本章控制输入(由 Planner/Composer 编译)
537
+ ${chapterIntent}
538
+
539
+ ### 已选上下文
540
+ ${selectedContext || "- none"}
541
+
542
+ ### 规则栈
543
+ - 硬护栏:${ruleStack.sections.hard.join("、") || "(无)"}
544
+ - 软约束:${ruleStack.sections.soft.join("、") || "(无)"}
545
+ - 诊断规则:${ruleStack.sections.diagnostic.join("、") || "(无)"}
546
+
547
+ ### 当前覆盖
548
+ ${overrides}\n`;
549
+ }
371
550
  extractBalancedJson(text) {
372
551
  const start = text.indexOf("{");
373
552
  if (start === -1)
@@ -383,7 +562,7 @@ ${chapterContent}`;
383
562
  }
384
563
  return null;
385
564
  }
386
- tryParseAuditJson(json) {
565
+ tryParseAuditJson(json, language = "zh") {
387
566
  try {
388
567
  const parsed = JSON.parse(json);
389
568
  if (typeof parsed.passed !== "boolean" && parsed.passed !== undefined)
@@ -393,7 +572,7 @@ ${chapterContent}`;
393
572
  issues: Array.isArray(parsed.issues)
394
573
  ? parsed.issues.map((i) => ({
395
574
  severity: i.severity ?? "warning",
396
- category: i.category ?? "未分类",
575
+ category: i.category ?? (language === "en" ? "Uncategorized" : "未分类"),
397
576
  description: i.description ?? "",
398
577
  suggestion: i.suggestion ?? "",
399
578
  }))