@actalk/inkos-core 1.4.1 → 1.5.0-canary.47.1

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 (262) hide show
  1. package/dist/agent/agent-session.d.ts +15 -0
  2. package/dist/agent/agent-session.d.ts.map +1 -1
  3. package/dist/agent/agent-session.js +256 -26
  4. package/dist/agent/agent-session.js.map +1 -1
  5. package/dist/agent/agent-system-prompt.d.ts +8 -1
  6. package/dist/agent/agent-system-prompt.d.ts.map +1 -1
  7. package/dist/agent/agent-system-prompt.js +382 -176
  8. package/dist/agent/agent-system-prompt.js.map +1 -1
  9. package/dist/agent/agent-tools.d.ts +156 -19
  10. package/dist/agent/agent-tools.d.ts.map +1 -1
  11. package/dist/agent/agent-tools.js +980 -46
  12. package/dist/agent/agent-tools.js.map +1 -1
  13. package/dist/agent/context-transform.d.ts +4 -1
  14. package/dist/agent/context-transform.d.ts.map +1 -1
  15. package/dist/agent/context-transform.js +104 -9
  16. package/dist/agent/context-transform.js.map +1 -1
  17. package/dist/agent/index.d.ts +1 -1
  18. package/dist/agent/index.d.ts.map +1 -1
  19. package/dist/agent/index.js +1 -1
  20. package/dist/agent/index.js.map +1 -1
  21. package/dist/agents/architect.d.ts +11 -1
  22. package/dist/agents/architect.d.ts.map +1 -1
  23. package/dist/agents/architect.js +242 -114
  24. package/dist/agents/architect.js.map +1 -1
  25. package/dist/agents/chapter-analyzer.js +1 -1
  26. package/dist/agents/chapter-analyzer.js.map +1 -1
  27. package/dist/agents/composer.d.ts +36 -0
  28. package/dist/agents/composer.d.ts.map +1 -1
  29. package/dist/agents/composer.js +503 -20
  30. package/dist/agents/composer.js.map +1 -1
  31. package/dist/agents/continuity.d.ts +3 -0
  32. package/dist/agents/continuity.d.ts.map +1 -1
  33. package/dist/agents/continuity.js +28 -14
  34. package/dist/agents/continuity.js.map +1 -1
  35. package/dist/agents/en-prompt-sections.d.ts.map +1 -1
  36. package/dist/agents/en-prompt-sections.js +15 -1
  37. package/dist/agents/en-prompt-sections.js.map +1 -1
  38. package/dist/agents/fanfic-canon-importer.d.ts +1 -0
  39. package/dist/agents/fanfic-canon-importer.d.ts.map +1 -1
  40. package/dist/agents/fanfic-canon-importer.js +53 -6
  41. package/dist/agents/fanfic-canon-importer.js.map +1 -1
  42. package/dist/agents/foundation-reviewer.d.ts +1 -0
  43. package/dist/agents/foundation-reviewer.d.ts.map +1 -1
  44. package/dist/agents/foundation-reviewer.js +17 -12
  45. package/dist/agents/foundation-reviewer.js.map +1 -1
  46. package/dist/agents/length-normalizer.d.ts +1 -0
  47. package/dist/agents/length-normalizer.d.ts.map +1 -1
  48. package/dist/agents/length-normalizer.js +16 -3
  49. package/dist/agents/length-normalizer.js.map +1 -1
  50. package/dist/agents/planner-prompts.d.ts +7 -7
  51. package/dist/agents/planner-prompts.d.ts.map +1 -1
  52. package/dist/agents/planner-prompts.js +29 -29
  53. package/dist/agents/planner-prompts.js.map +1 -1
  54. package/dist/agents/planner.d.ts +6 -5
  55. package/dist/agents/planner.d.ts.map +1 -1
  56. package/dist/agents/planner.js +90 -6
  57. package/dist/agents/planner.js.map +1 -1
  58. package/dist/agents/post-write-validator.d.ts.map +1 -1
  59. package/dist/agents/post-write-validator.js +49 -0
  60. package/dist/agents/post-write-validator.js.map +1 -1
  61. package/dist/agents/reviser.js +10 -0
  62. package/dist/agents/reviser.js.map +1 -1
  63. package/dist/agents/rules-reader.d.ts +6 -14
  64. package/dist/agents/rules-reader.d.ts.map +1 -1
  65. package/dist/agents/rules-reader.js +15 -28
  66. package/dist/agents/rules-reader.js.map +1 -1
  67. package/dist/agents/short-fiction.d.ts +4 -0
  68. package/dist/agents/short-fiction.d.ts.map +1 -1
  69. package/dist/agents/short-fiction.js +51 -8
  70. package/dist/agents/short-fiction.js.map +1 -1
  71. package/dist/agents/state-validator.d.ts +0 -2
  72. package/dist/agents/state-validator.d.ts.map +1 -1
  73. package/dist/agents/state-validator.js +4 -16
  74. package/dist/agents/state-validator.js.map +1 -1
  75. package/dist/agents/style-analyzer.d.ts +1 -1
  76. package/dist/agents/style-analyzer.d.ts.map +1 -1
  77. package/dist/agents/style-analyzer.js +34 -17
  78. package/dist/agents/style-analyzer.js.map +1 -1
  79. package/dist/agents/writer-prompts.d.ts.map +1 -1
  80. package/dist/agents/writer-prompts.js +160 -12
  81. package/dist/agents/writer-prompts.js.map +1 -1
  82. package/dist/agents/writer.d.ts.map +1 -1
  83. package/dist/agents/writer.js +31 -9
  84. package/dist/agents/writer.js.map +1 -1
  85. package/dist/index.d.ts +18 -7
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +17 -7
  88. package/dist/index.js.map +1 -1
  89. package/dist/interaction/action-envelope.d.ts +261 -0
  90. package/dist/interaction/action-envelope.d.ts.map +1 -0
  91. package/dist/interaction/action-envelope.js +102 -0
  92. package/dist/interaction/action-envelope.js.map +1 -0
  93. package/dist/interaction/book-session-store.d.ts +6 -2
  94. package/dist/interaction/book-session-store.d.ts.map +1 -1
  95. package/dist/interaction/book-session-store.js +21 -3
  96. package/dist/interaction/book-session-store.js.map +1 -1
  97. package/dist/interaction/edit-controller.d.ts +5 -0
  98. package/dist/interaction/edit-controller.d.ts.map +1 -1
  99. package/dist/interaction/edit-controller.js +123 -26
  100. package/dist/interaction/edit-controller.js.map +1 -1
  101. package/dist/interaction/events.d.ts +4 -4
  102. package/dist/interaction/intents.d.ts +9 -6
  103. package/dist/interaction/intents.d.ts.map +1 -1
  104. package/dist/interaction/intents.js +2 -1
  105. package/dist/interaction/intents.js.map +1 -1
  106. package/dist/interaction/project-control.d.ts +3 -43
  107. package/dist/interaction/project-control.d.ts.map +1 -1
  108. package/dist/interaction/project-control.js +1 -53
  109. package/dist/interaction/project-control.js.map +1 -1
  110. package/dist/interaction/project-tools.d.ts +1 -1
  111. package/dist/interaction/project-tools.d.ts.map +1 -1
  112. package/dist/interaction/project-tools.js +41 -185
  113. package/dist/interaction/project-tools.js.map +1 -1
  114. package/dist/interaction/runtime.d.ts +1 -1
  115. package/dist/interaction/runtime.d.ts.map +1 -1
  116. package/dist/interaction/runtime.js +49 -75
  117. package/dist/interaction/runtime.js.map +1 -1
  118. package/dist/interaction/session-transcript-legacy.d.ts.map +1 -1
  119. package/dist/interaction/session-transcript-legacy.js +2 -0
  120. package/dist/interaction/session-transcript-legacy.js.map +1 -1
  121. package/dist/interaction/session-transcript-restore.d.ts +4 -3
  122. package/dist/interaction/session-transcript-restore.d.ts.map +1 -1
  123. package/dist/interaction/session-transcript-restore.js +234 -34
  124. package/dist/interaction/session-transcript-restore.js.map +1 -1
  125. package/dist/interaction/session-transcript-schema.d.ts +45 -12
  126. package/dist/interaction/session-transcript-schema.d.ts.map +1 -1
  127. package/dist/interaction/session-transcript-schema.js +6 -0
  128. package/dist/interaction/session-transcript-schema.js.map +1 -1
  129. package/dist/interaction/session-transcript.d.ts +8 -1
  130. package/dist/interaction/session-transcript.d.ts.map +1 -1
  131. package/dist/interaction/session-transcript.js +13 -1
  132. package/dist/interaction/session-transcript.js.map +1 -1
  133. package/dist/interaction/session.d.ts +78 -66
  134. package/dist/interaction/session.d.ts.map +1 -1
  135. package/dist/interaction/session.js +10 -2
  136. package/dist/interaction/session.js.map +1 -1
  137. package/dist/llm/provider.d.ts +32 -34
  138. package/dist/llm/provider.d.ts.map +1 -1
  139. package/dist/llm/provider.js +144 -127
  140. package/dist/llm/provider.js.map +1 -1
  141. package/dist/models/book-rules.d.ts +6 -4
  142. package/dist/models/book-rules.d.ts.map +1 -1
  143. package/dist/models/book-rules.js +187 -8
  144. package/dist/models/book-rules.js.map +1 -1
  145. package/dist/models/context-compression.d.ts +13 -0
  146. package/dist/models/context-compression.d.ts.map +1 -0
  147. package/dist/models/context-compression.js +2 -0
  148. package/dist/models/context-compression.js.map +1 -0
  149. package/dist/models/input-governance.d.ts +53 -12
  150. package/dist/models/input-governance.d.ts.map +1 -1
  151. package/dist/models/input-governance.js +16 -0
  152. package/dist/models/input-governance.js.map +1 -1
  153. package/dist/models/play.d.ts +530 -0
  154. package/dist/models/play.d.ts.map +1 -0
  155. package/dist/models/play.js +318 -0
  156. package/dist/models/play.js.map +1 -0
  157. package/dist/models/project.d.ts +8 -0
  158. package/dist/models/project.d.ts.map +1 -1
  159. package/dist/models/project.js +1 -0
  160. package/dist/models/project.js.map +1 -1
  161. package/dist/pipeline/chapter-review-cycle.d.ts.map +1 -1
  162. package/dist/pipeline/chapter-review-cycle.js +29 -3
  163. package/dist/pipeline/chapter-review-cycle.js.map +1 -1
  164. package/dist/pipeline/persisted-governed-plan.d.ts.map +1 -1
  165. package/dist/pipeline/persisted-governed-plan.js +98 -49
  166. package/dist/pipeline/persisted-governed-plan.js.map +1 -1
  167. package/dist/pipeline/runner.d.ts +31 -0
  168. package/dist/pipeline/runner.d.ts.map +1 -1
  169. package/dist/pipeline/runner.js +212 -68
  170. package/dist/pipeline/runner.js.map +1 -1
  171. package/dist/pipeline/short-fiction-runner.d.ts +14 -0
  172. package/dist/pipeline/short-fiction-runner.d.ts.map +1 -1
  173. package/dist/pipeline/short-fiction-runner.js +242 -94
  174. package/dist/pipeline/short-fiction-runner.js.map +1 -1
  175. package/dist/play/play-agents.d.ts +71 -0
  176. package/dist/play/play-agents.d.ts.map +1 -0
  177. package/dist/play/play-agents.js +511 -0
  178. package/dist/play/play-agents.js.map +1 -0
  179. package/dist/play/play-db-factory.d.ts +9 -0
  180. package/dist/play/play-db-factory.d.ts.map +1 -0
  181. package/dist/play/play-db-factory.js +18 -0
  182. package/dist/play/play-db-factory.js.map +1 -0
  183. package/dist/play/play-db.d.ts +22 -0
  184. package/dist/play/play-db.d.ts.map +1 -0
  185. package/dist/play/play-db.js +248 -0
  186. package/dist/play/play-db.js.map +1 -0
  187. package/dist/play/play-file-db.d.ts +32 -0
  188. package/dist/play/play-file-db.d.ts.map +1 -0
  189. package/dist/play/play-file-db.js +156 -0
  190. package/dist/play/play-file-db.js.map +1 -0
  191. package/dist/play/play-image.d.ts +58 -0
  192. package/dist/play/play-image.d.ts.map +1 -0
  193. package/dist/play/play-image.js +142 -0
  194. package/dist/play/play-image.js.map +1 -0
  195. package/dist/play/play-reducer.d.ts +31 -0
  196. package/dist/play/play-reducer.d.ts.map +1 -0
  197. package/dist/play/play-reducer.js +261 -0
  198. package/dist/play/play-reducer.js.map +1 -0
  199. package/dist/play/play-runner.d.ts +102 -0
  200. package/dist/play/play-runner.d.ts.map +1 -0
  201. package/dist/play/play-runner.js +465 -0
  202. package/dist/play/play-runner.js.map +1 -0
  203. package/dist/play/play-store.d.ts +112 -0
  204. package/dist/play/play-store.d.ts.map +1 -0
  205. package/dist/play/play-store.js +311 -0
  206. package/dist/play/play-store.js.map +1 -0
  207. package/dist/prompts/short-fiction.d.ts +5 -0
  208. package/dist/prompts/short-fiction.d.ts.map +1 -1
  209. package/dist/prompts/short-fiction.js +46 -22
  210. package/dist/prompts/short-fiction.js.map +1 -1
  211. package/dist/state/state-bootstrap.d.ts.map +1 -1
  212. package/dist/state/state-bootstrap.js +12 -25
  213. package/dist/state/state-bootstrap.js.map +1 -1
  214. package/dist/state/state-reducer.js +31 -22
  215. package/dist/state/state-reducer.js.map +1 -1
  216. package/dist/utils/book-eval.d.ts +35 -0
  217. package/dist/utils/book-eval.d.ts.map +1 -0
  218. package/dist/utils/book-eval.js +116 -0
  219. package/dist/utils/book-eval.js.map +1 -0
  220. package/dist/utils/chapter-memo-parser.d.ts +10 -7
  221. package/dist/utils/chapter-memo-parser.d.ts.map +1 -1
  222. package/dist/utils/chapter-memo-parser.js +86 -43
  223. package/dist/utils/chapter-memo-parser.js.map +1 -1
  224. package/dist/utils/context-assembly.d.ts +2 -0
  225. package/dist/utils/context-assembly.d.ts.map +1 -1
  226. package/dist/utils/context-assembly.js +38 -1
  227. package/dist/utils/context-assembly.js.map +1 -1
  228. package/dist/utils/hook-health.d.ts.map +1 -1
  229. package/dist/utils/hook-health.js +5 -2
  230. package/dist/utils/hook-health.js.map +1 -1
  231. package/dist/utils/hook-ledger-validator.d.ts +1 -1
  232. package/dist/utils/hook-ledger-validator.d.ts.map +1 -1
  233. package/dist/utils/hook-ledger-validator.js +5 -5
  234. package/dist/utils/hook-ledger-validator.js.map +1 -1
  235. package/dist/utils/hook-lifecycle.d.ts +1 -0
  236. package/dist/utils/hook-lifecycle.d.ts.map +1 -1
  237. package/dist/utils/hook-lifecycle.js +10 -3
  238. package/dist/utils/hook-lifecycle.js.map +1 -1
  239. package/dist/utils/language.d.ts +10 -0
  240. package/dist/utils/language.d.ts.map +1 -0
  241. package/dist/utils/language.js +18 -0
  242. package/dist/utils/language.js.map +1 -0
  243. package/dist/utils/length-metrics.d.ts +3 -0
  244. package/dist/utils/length-metrics.d.ts.map +1 -1
  245. package/dist/utils/length-metrics.js +8 -0
  246. package/dist/utils/length-metrics.js.map +1 -1
  247. package/dist/utils/memory-retrieval.d.ts.map +1 -1
  248. package/dist/utils/memory-retrieval.js +19 -15
  249. package/dist/utils/memory-retrieval.js.map +1 -1
  250. package/dist/utils/outline-paths.d.ts +12 -0
  251. package/dist/utils/outline-paths.d.ts.map +1 -1
  252. package/dist/utils/outline-paths.js +68 -0
  253. package/dist/utils/outline-paths.js.map +1 -1
  254. package/package.json +1 -1
  255. package/dist/interaction/nl-router.d.ts +0 -8
  256. package/dist/interaction/nl-router.d.ts.map +0 -1
  257. package/dist/interaction/nl-router.js +0 -218
  258. package/dist/interaction/nl-router.js.map +0 -1
  259. package/dist/pipeline/agent.d.ts +0 -15
  260. package/dist/pipeline/agent.d.ts.map +0 -1
  261. package/dist/pipeline/agent.js +0 -597
  262. package/dist/pipeline/agent.js.map +0 -1
@@ -4,24 +4,25 @@ import { writeFile, mkdir, rm } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
5
  import { renderHookSnapshot } from "../utils/memory-retrieval.js";
6
6
  import { shouldPromoteHook, } from "../utils/hook-promotion.js";
7
- /**
8
- * Split a markdown string into its leading YAML frontmatter block and the
9
- * remaining body. Returns `frontmatter: null` when no frontmatter is present.
10
- * Only recognises a frontmatter block that starts on the FIRST non-empty
11
- * line embedded `---` sections in prose are left alone.
12
- */
13
- function extractYamlFrontmatter(raw) {
14
- if (!raw)
15
- return { frontmatter: null, body: "" };
16
- const stripped = raw.replace(/^```(?:md|markdown|yaml)?\s*\n/, "").replace(/\n```\s*$/, "");
17
- const leadingMatch = stripped.match(/^\s*---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
18
- if (!leadingMatch) {
19
- return { frontmatter: null, body: stripped };
7
+ export class ArchitectIncompleteFoundationError extends Error {
8
+ missing;
9
+ partialContent;
10
+ constructor(missing, partialContent, message) {
11
+ super(message ?? `Architect foundation incomplete; missing sections: ${missing.join(", ")}`);
12
+ this.name = "ArchitectIncompleteFoundationError";
13
+ this.missing = missing;
14
+ this.partialContent = partialContent;
15
+ }
16
+ }
17
+ class MissingArchitectSectionsError extends Error {
18
+ missing;
19
+ content;
20
+ constructor(missing, content) {
21
+ super(`Architect output missing required section${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`);
22
+ this.name = "MissingArchitectSectionsError";
23
+ this.missing = missing;
24
+ this.content = content;
20
25
  }
21
- return {
22
- frontmatter: `---\n${leadingMatch[1]}\n---`,
23
- body: leadingMatch[2].trim(),
24
- };
25
26
  }
26
27
  export class ArchitectAgent extends BaseAgent {
27
28
  get name() {
@@ -38,10 +39,10 @@ export class ArchitectAgent extends BaseAgent {
38
39
  ? this.buildRevisePrompt(options.reviseFrom)
39
40
  : "";
40
41
  const numericalBlock = gp.numericalSystem
41
- ? "- 有明确的数值/资源体系可追踪\n- 在 book_rules 中定义 numericalSystemOverrides(hardCap、resourceTypes)"
42
+ ? "- 有明确的数值/资源体系可追踪\n- 在 book_rules 中写清核心资源、硬上限和不可突破规则"
42
43
  : "- 本题材无数值系统,不需要资源账本";
43
44
  const powerBlock = gp.powerScaling ? "- 有明确的战力等级体系" : "";
44
- const eraBlock = gp.eraResearch ? "- 需要年代考据支撑(在 book_rules 中设置 eraConstraints)" : "";
45
+ const eraBlock = gp.eraResearch ? "- 需要年代考据支撑(在 story_frame 中织入时代锚,在 book_rules 中写清不可违背的年代限制)" : "";
45
46
  const systemPrompt = resolvedLanguage === "en"
46
47
  ? this.buildEnglishFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock)
47
48
  : this.buildChineseFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock);
@@ -55,7 +56,7 @@ export class ArchitectAgent extends BaseAgent {
55
56
  { role: "system", content: langPrefix + systemPrompt + revisePrompt },
56
57
  { role: "user", content: userMessage },
57
58
  ], { temperature: 0.8 });
58
- return this.parseSections(response.content, resolvedLanguage);
59
+ return this.parseSectionsWithRepair(response.content, resolvedLanguage);
59
60
  }
60
61
  buildRevisePrompt(reviseFrom) {
61
62
  return `\n\n## 既有架构稿修订模式
@@ -117,7 +118,7 @@ ${eraBlock}
117
118
  - story_frame ≤ 3000 chars
118
119
  - volume_map ≤ 5000 chars
119
120
  - roles 总 ≤ 8000 chars
120
- - book_rules ≤ 500 chars(仅 YAML)
121
+ - book_rules ≤ 1000 chars(普通 Markdown 规则卡)
121
122
  - pending_hooks ≤ 2000 chars
122
123
 
123
124
  === SECTION: story_frame ===
@@ -130,7 +131,7 @@ ${eraBlock}
130
131
  ### 段 2:核心冲突、对手定性、前台/后台双层故事
131
132
  这本书的主要矛盾是什么?不是"正邪对抗",而是"因为 A 相信 X、B 相信 Y,所以他们一定会在某件事上对撞"。主要对手是谁(至少 2 个:一个显性对手 + 一个结构性对手/体制),他们的动机从哪里长出来。对手不是工具,对手有自己的逻辑。
132
133
 
133
- **本段必须显式写出"前台故事 / 后台故事"两条线**(番茄老师弈青锋的"台前台后"分层法):
134
+ **本段必须显式写出"前台故事 / 后台故事"两条线**:
134
135
  - **前台故事**:读者每章看得到的表层冲突(查案、打怪、升级、谈恋爱、搞事业等),每个卷/arc 有独立的显性目标和完结点
135
136
  - **后台故事**:贯穿全书的暗线——藏在所有前台事件背后的那台"机器"(幕后黑手、阴谋、身世秘密、体制压迫、命运诅咒等),读者只能通过碎片拼出来,大结局时才整体兑现
136
137
 
@@ -142,7 +143,7 @@ ${eraBlock}
142
143
  ### 段 4:终局方向 + 全书 Objective(OKR 大纲的根)
143
144
  这本书最后一章大概是什么感觉——不是"主角登顶"、"大结局"这种套话,而是**最后一个镜头**大致长什么样。主角最后在哪、做什么、身边有谁、心里想什么。这是给全书所有后面的规划一个远方靶子。
144
145
 
145
- **本段末尾必须明确写出全书 Objective 一句话**(番茄老师弈青锋的 OKR 递归大纲法):这本书讲完时,主角必须达成一个**可验证的终局状态**(例:"从一个杂役修士成为宗门长老并公开父辈冤案的真相"、"从黑户打工妹成为掌控三家皮草公司的老板娘并亲手送前夫进监狱")。不要写"变强"、"复仇"这类抽象词,要写**一个能被外部观察者判定"达成 / 未达成"的具体状态**。这个 Objective 是全书 OKR 递归大纲的根——下面 volume_map 的每一卷会分解出这个 O 对应的 Key Results。
146
+ **本段末尾必须明确写出全书 Objective 一句话**:这本书讲完时,主角必须达成一个**可验证的终局状态**(例:"从一个杂役修士成为宗门长老并公开父辈冤案的真相"、"从黑户打工妹成为掌控三家皮草公司的老板娘并亲手送前夫进监狱")。不要写"变强"、"复仇"这类抽象词,要写**一个能被外部观察者判定"达成 / 未达成"的具体状态**。这个 Objective 是全书递归大纲的根——下面 volume_map 的每一卷会分解出这个 O 对应的 Key Results。
146
147
 
147
148
  === SECTION: volume_map ===
148
149
 
@@ -233,28 +234,29 @@ name: <次要角色名>
233
234
 
234
235
  === SECTION: book_rules ===
235
236
 
236
- **只输出 YAML frontmatter 一块——零散文。** 所有的"叙事视角 / 本书专属规则 / 核心冲突驱动"等散文已经合并到 story_frame.世界观底色,不要在这里重复写。
237
- \`\`\`
238
- ---
239
- version: "1.0"
240
- protagonist:
241
- name: (主角名)
242
- personalityLock: [(3-5个性格关键词)]
243
- behavioralConstraints: [(3-5条行为约束)]
244
- genreLock:
245
- primary: ${book.genre}
246
- forbidden: [(2-3种禁止混入的文风)]
247
- ${gp.numericalSystem ? `numericalSystemOverrides:
248
- hardCap: (根据设定确定)
249
- resourceTypes: [(核心资源类型列表)]` : ""}
250
- prohibitions:
251
- - (3-5条本书禁忌)
252
- chapterTypesOverride: []
253
- fatigueWordsOverride: []
254
- additionalAuditDimensions: []
255
- enableFullCastTracking: false
256
- ---
257
- \`\`\`
237
+ 输出普通 Markdown,不要 YAML frontmatter,不要 JSON,不要代码块。这里只写给运行时和写手都能读懂的规则卡,不写长篇散文;叙事视角、核心冲突驱动等长说明已经在 story_frame.世界观底色里写过,这里只保留可执行规则。
238
+
239
+ ## 主角
240
+ - 名字:<主角名>
241
+ - 性格锁:<3-5 个性格关键词,用顿号分隔>
242
+ - 行为约束:<3-5 条主角不能违背的行为边界,用顿号分隔>
243
+
244
+ ## 题材锁
245
+ - 主类型:${book.genre}
246
+ - 禁止混入:<2-3 种禁止混入的文风/体系>
247
+
248
+ ## 叙事人称
249
+ <只有当用户明确指定第一人称或第三人称时才写;没指定就写"无">
250
+
251
+ ${gp.numericalSystem ? `## 数值/资源规则
252
+ - 核心资源:<核心资源类型>
253
+ - 硬上限:<根据设定确定,不能随剧情突破>` : ""}
254
+
255
+ ${gp.eraResearch ? `## 年代限制
256
+ - <2-3 条必须符合年代/政策/物价/社会环境的约束>` : ""}
257
+
258
+ ## 禁止事项
259
+ - <3-5 条本书禁忌>
258
260
 
259
261
  === SECTION: pending_hooks ===
260
262
 
@@ -264,6 +266,7 @@ enableFullCastTracking: false
264
266
  伏笔表规则:
265
267
  - 第5列必须是纯数字章节号,不能写自然语言描述
266
268
  - 建书阶段所有伏笔都还没正式推进,所以第5列统一填 0
269
+ - 普通种子行不要写 open;尚未被正文真正推进的普通伏笔状态写「暂缓」。只有主线承重、依赖链或跨卷结构需要系统预升级时,运行时才会把它视为活跃伏笔
267
270
  - 第7列必须填写:立即 / 近期 / 中程 / 慢烧 / 终局 之一
268
271
  - 第8列「上游依赖」:列出必须在本伏笔之前种下/回收的上游 hook_id,格式如 [H003, H007];若无依赖填「无」
269
272
  - 第9列「回收卷」:用自然语言写该伏笔计划在哪一卷哪一段回收(例:"第2卷中段"、"终卷终章前")。不强制解析为章号
@@ -277,12 +280,12 @@ enableFullCastTracking: false
277
280
  - 主角人设鲜明、行为边界清晰
278
281
  - 伏笔前后呼应、配角有独立动机不是工具人
279
282
  - **story_frame / volume_map / roles 必须是散文密度,不要退化成 bullet**
280
- - **book_rules 只留 YAML,不要写散文**
283
+ - **book_rules 用普通 Markdown 规则卡,不要 YAML/JSON/代码块,也不要写成长篇散文**
281
284
  - **不要输出 rhythm_principles 或 current_state 独立 section**——节奏原则合并进 volume_map 尾段;角色初始状态写在 roles.当前现状,初始钩子写在 pending_hooks(startChapter=0 行),环境/时代锚(仅历史/年代/都市重生等需要年份的题材)织进 story_frame.世界观底色,不要硬凑
282
285
  - **pending_hooks 表必须包含 Phase 7 扩展列——depends_on 标出因果链、pays_off_in_arc 锁定回收大致位置、core_hook 标记主线承重伏笔(3-7 条)、half_life 仅给重点伏笔设置**
283
286
 
284
287
  ## 硬性完结检查(生成前读一遍)
285
- 必须依次输出全部 **5 个 SECTION 块**:story_frame → volume_map → roles → book_rules → pending_hooks,不允许因为 story_frame 或 volume_map 写长了就不写后 3 段。哪怕 roles 只列 3 个角色、book_rules 只有 YAML 小块、pending_hooks 只有 3 行,也要完整输出。只有写完 pending_hooks 最后一行才算交付。`;
288
+ 必须依次输出全部 **5 个 SECTION 块**:story_frame → volume_map → roles → book_rules → pending_hooks,不允许因为 story_frame 或 volume_map 写长了就不写后 3 段。哪怕 roles 只列 3 个角色、book_rules 只有 Markdown 小块、pending_hooks 只有 3 行,也要完整输出。只有写完 pending_hooks 最后一行才算交付。`;
286
289
  }
287
290
  buildEnglishFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock) {
288
291
  return `You are the architect of this book. Your only job is to produce **prose-density foundation design** — not tables, not schema, not bullet lists. The book's aura comes from your prose density: Phase 3 planner reads sparse memos out of your volume_map only if it was written to chapter-level prose; the writer only produces living characters because your role sheets carry contrast details; the reviewer only catches hard errors because your story_frame set the tonal anchors.${contextBlock}${reviewFeedbackBlock}
@@ -311,7 +314,7 @@ Do not duplicate the same fact across sections. The protagonist's arc lives only
311
314
  - story_frame ≤ 3000 chars
312
315
  - volume_map ≤ 5000 chars
313
316
  - roles ≤ 8000 chars total
314
- - book_rules ≤ 500 chars (YAML only)
317
+ - book_rules ≤ 1000 chars (ordinary Markdown rules card)
315
318
  - pending_hooks ≤ 2000 chars
316
319
 
317
320
  === SECTION: story_frame ===
@@ -419,28 +422,29 @@ name: <minor name>
419
422
 
420
423
  === SECTION: book_rules ===
421
424
 
422
- **Output ONLY the YAML frontmatter block zero prose.** All narrative guidance (perspective, book-specific rules, core conflict driver) has moved into story_frame.03_World_Tonal_Ground. Do not repeat it here.
423
- \`\`\`
424
- ---
425
- version: "1.0"
426
- protagonist:
427
- name: (protagonist name)
428
- personalityLock: [(3-5 personality keywords)]
429
- behavioralConstraints: [(3-5 behavioral constraints)]
430
- genreLock:
431
- primary: ${book.genre}
432
- forbidden: [(2-3 forbidden style intrusions)]
433
- ${gp.numericalSystem ? `numericalSystemOverrides:
434
- hardCap: (decide from setting)
435
- resourceTypes: [(core resource types)]` : ""}
436
- prohibitions:
437
- - (3-5 book-specific prohibitions)
438
- chapterTypesOverride: []
439
- fatigueWordsOverride: []
440
- additionalAuditDimensions: []
441
- enableFullCastTracking: false
442
- ---
443
- \`\`\`
425
+ Output ordinary Markdown. Do NOT output YAML frontmatter, JSON, or code fences. This is a compact rules card readable by both runtime and writers; long narrative guidance already lives in story_frame.03_World_Tonal_Ground.
426
+
427
+ ## Protagonist
428
+ - Name: <protagonist name>
429
+ - Personality lock: <3-5 personality keywords, comma-separated>
430
+ - Behavioral constraints: <3-5 behavioral boundaries>
431
+
432
+ ## Genre Lock
433
+ - Primary: ${book.genre}
434
+ - Forbidden: <2-3 forbidden style/system intrusions>
435
+
436
+ ## Narrative Person
437
+ <Write first person or third person ONLY if the user explicitly requested it; otherwise write "none".>
438
+
439
+ ${gp.numericalSystem ? `## Numerical / Resource Rules
440
+ - Core resources: <core resource types>
441
+ - Hard cap: <setting-specific cap that cannot be broken by plot convenience>` : ""}
442
+
443
+ ${gp.eraResearch ? `## Era Constraints
444
+ - <2-3 constraints tied to policy, prices, technology, or social environment>` : ""}
445
+
446
+ ## Prohibitions
447
+ - <3-5 book-specific prohibitions>
444
448
 
445
449
  === SECTION: pending_hooks ===
446
450
 
@@ -450,6 +454,7 @@ Initial hook pool (Markdown table), Phase 7 extended columns:
450
454
  Rules:
451
455
  - Column 5 is a pure chapter number, not narrative description
452
456
  - At book creation all planned hooks have last_advanced_chapter = 0
457
+ - Ordinary seed rows must not use status "open"; use "deferred" until prose actually advances them. Only load-bearing core / dependency / cross-volume hooks may be pre-promoted by the runtime into active hook debt
453
458
  - Column 7 must be: immediate / near-term / mid-arc / slow-burn / endgame
454
459
  - Column 8 (depends_on): upstream hook ids that must be planted / paid off before this one fires, formatted [H003, H007]; write "none" if no upstream
455
460
  - Column 9 (pays_off_in_arc): free-form prose on where this hook is scheduled to pay off (e.g. "mid of volume 2", "right before the finale"). NOT parsed into chapter numbers
@@ -463,28 +468,74 @@ Rules:
463
468
  - Protagonist persona clear with sharp behavioral boundaries
464
469
  - Hooks planted with payoff promises; supporting characters have independent motivation
465
470
  - **story_frame / volume_map / roles must be prose density — no bullet-list degradation**
466
- - **book_rules is YAML only — no prose body**
471
+ - **book_rules is an ordinary Markdown rules card — no YAML, JSON, code fence, or long prose**
467
472
  - **Do NOT emit rhythm_principles or current_state as separate sections** — rhythm principles live in the last paragraph of volume_map; character initial status goes in roles.Current_State; initial hooks go in pending_hooks (start_chapter=0 rows); environment / era anchors (only when the genre has a real year) are woven into story_frame's world-tonal-ground paragraph
468
473
  - **pending_hooks table MUST carry Phase 7 extended columns — depends_on spells out the causal chain, pays_off_in_arc locks the approximate payoff location, core_hook marks main-line load-bearing hooks (3-7 per book), half_life only on priority hooks**
469
474
 
470
475
  ## Hard completeness check (read before generating)
471
- You MUST emit all **5 SECTION blocks in order**: story_frame → volume_map → roles → book_rules → pending_hooks. Do NOT stop after story_frame or volume_map just because they ran long. Even if roles lists only 3 characters, book_rules is a tiny YAML block, and pending_hooks has only 3 rows, all five must appear. The output is only considered delivered after the last row of pending_hooks is written.`;
476
+ You MUST emit all **5 SECTION blocks in order**: story_frame → volume_map → roles → book_rules → pending_hooks. Do NOT stop after story_frame or volume_map just because they ran long. Even if roles lists only 3 characters, book_rules is a small Markdown block, and pending_hooks has only 3 rows, all five must appear. The output is only considered delivered after the last row of pending_hooks is written.`;
472
477
  }
473
478
  // -------------------------------------------------------------------------
474
479
  // Parsing
475
480
  // -------------------------------------------------------------------------
476
- parseSections(content, language) {
477
- const parsedSections = new Map();
478
- const sectionPattern = /^\s*===\s*SECTION\s*[::]\s*([^\n=]+?)\s*===\s*$/gim;
479
- const matches = [...content.matchAll(sectionPattern)];
480
- for (let i = 0; i < matches.length; i++) {
481
- const match = matches[i];
482
- const rawName = match[1] ?? "";
483
- const start = (match.index ?? 0) + match[0].length;
484
- const end = matches[i + 1]?.index ?? content.length;
485
- const normalizedName = this.normalizeSectionName(rawName);
486
- parsedSections.set(normalizedName, content.slice(start, end).trim());
481
+ async parseSectionsWithRepair(content, language) {
482
+ try {
483
+ return this.parseSections(content, language);
487
484
  }
485
+ catch (error) {
486
+ if (!(error instanceof MissingArchitectSectionsError)) {
487
+ throw error;
488
+ }
489
+ const repaired = await this.repairMissingSections(error, language);
490
+ try {
491
+ return this.parseSections(repaired, language);
492
+ }
493
+ catch (repairError) {
494
+ if (repairError instanceof MissingArchitectSectionsError) {
495
+ const missing = repairError.missing.join("、");
496
+ const message = language === "en"
497
+ ? `The story foundation came back incomplete (missing: ${repairError.missing.join(", ")}). `
498
+ + "This usually means the model didn't write every section in one pass — it's not a problem with your input. "
499
+ + "Try again, or switch to a stronger model (e.g. deepseek-v4-pro / gpt-5.5) and regenerate."
500
+ : `基础设定没有生成完整(缺少:${missing})。`
501
+ + "这通常是模型一次没把所有部分写全,不是你的输入有问题。"
502
+ + "点重试,或换更强的模型(如 deepseek-v4-pro / gpt-5.5)再生成一次,通常就能解决。";
503
+ throw new ArchitectIncompleteFoundationError(repairError.missing, repairError.content, message);
504
+ }
505
+ throw repairError;
506
+ }
507
+ }
508
+ }
509
+ async repairMissingSections(error, language) {
510
+ const missingList = error.missing.join(", ");
511
+ const system = language === "en"
512
+ ? [
513
+ "You repair InkOS architect output formatting.",
514
+ "The previous draft is partially useful but is missing required SECTION blocks.",
515
+ "Do not invent a new book. Preserve usable existing content and add the missing parts.",
516
+ "Return the complete output with exactly these 5 SECTION blocks in order: story_frame, volume_map, roles, book_rules, pending_hooks.",
517
+ "book_rules must be ordinary Markdown, not YAML. pending_hooks must be a Markdown table.",
518
+ "Do not explain the repair.",
519
+ ].join("\n")
520
+ : [
521
+ "你负责修复 InkOS architect 的输出格式。",
522
+ "上一轮草稿有可用内容,但缺少必需的 SECTION 块。",
523
+ "不要重新发明一本书;保留已有可用内容,只补齐缺失部分并整理成完整输出。",
524
+ "必须按顺序返回完整 5 段 SECTION:story_frame、volume_map、roles、book_rules、pending_hooks。",
525
+ "book_rules 必须是普通 Markdown,不要 YAML;pending_hooks 必须是 Markdown 表格。",
526
+ "不要解释修复过程。",
527
+ ].join("\n");
528
+ const user = language === "en"
529
+ ? `Missing sections: ${missingList}\n\nOriginal partial output:\n\n${error.content}`
530
+ : `缺失 section:${missingList}\n\n原始不完整输出如下:\n\n${error.content}`;
531
+ const response = await this.chat([
532
+ { role: "system", content: system },
533
+ { role: "user", content: user },
534
+ ], { temperature: 0.2 });
535
+ return response.content;
536
+ }
537
+ parseSections(content, language) {
538
+ const parsedSections = this.parseArchitectSectionMap(content);
488
539
  // Phase 5 new sections take precedence.
489
540
  const storyFrame = parsedSections.get("story_frame") ?? "";
490
541
  const volumeMap = parsedSections.get("volume_map") ?? "";
@@ -529,7 +580,7 @@ You MUST emit all **5 SECTION blocks in order**: story_frame → volume_map →
529
580
  if (!pendingHooksRaw)
530
581
  missing.push("pending_hooks");
531
582
  if (missing.length > 0) {
532
- throw new Error(`Architect output missing required section${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`);
583
+ throw new MissingArchitectSectionsError(missing, content);
533
584
  }
534
585
  const roles = this.parseRoles(rolesRaw);
535
586
  const pendingHooks = this.normalizePendingHooksSection(this.stripTrailingAssistantCoda(pendingHooksRaw), effectiveVolumeMap);
@@ -552,6 +603,36 @@ You MUST emit all **5 SECTION blocks in order**: story_frame → volume_map →
552
603
  roles,
553
604
  };
554
605
  }
606
+ parseArchitectSectionMap(content) {
607
+ const sectionPattern = /^\s{0,3}(?:#{1,6}\s*)?===\s*SECTION\s*[::]\s*([^\n=]+?)\s*===\s*(?:#+\s*)?$/gim;
608
+ const markerMatches = [...content.matchAll(sectionPattern)].map((match) => ({
609
+ name: this.normalizeSectionName(match[1] ?? ""),
610
+ index: match.index ?? 0,
611
+ markerLength: match[0].length,
612
+ }));
613
+ if (markerMatches.length > 0) {
614
+ return this.sliceArchitectSections(content, markerMatches);
615
+ }
616
+ const headingPattern = /^\s{0,3}#{1,3}\s+(.+?)\s*$/gim;
617
+ const headingMatches = [...content.matchAll(headingPattern)]
618
+ .map((match) => ({
619
+ name: this.canonicalSectionNameFromHeading(match[1] ?? ""),
620
+ index: match.index ?? 0,
621
+ markerLength: match[0].length,
622
+ }))
623
+ .filter((match) => Boolean(match.name));
624
+ return this.sliceArchitectSections(content, headingMatches);
625
+ }
626
+ sliceArchitectSections(content, matches) {
627
+ const parsedSections = new Map();
628
+ for (let i = 0; i < matches.length; i++) {
629
+ const match = matches[i];
630
+ const start = match.index + match.markerLength;
631
+ const end = matches[i + 1]?.index ?? content.length;
632
+ parsedSections.set(match.name, content.slice(start, end).trim());
633
+ }
634
+ return parsedSections;
635
+ }
555
636
  /**
556
637
  * Parse ---ROLE---...---CONTENT---... blocks from the roles section.
557
638
  * Drops malformed entries silently — this is prose the LLM produced,
@@ -597,19 +678,6 @@ You MUST emit all **5 SECTION blocks in order**: story_frame → volume_map →
597
678
  }
598
679
  return `# 角色矩阵(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威来源已迁移至 roles/ 文件夹(一人一卡)。\n\n## 主要角色\n\n${majorLines.join("\n") || "(无)"}\n\n## 次要角色\n\n${minorLines.join("\n") || "(无)"}\n`;
599
680
  }
600
- buildBookRulesShim(bookRulesBody, language) {
601
- const trimmedBody = bookRulesBody.trim();
602
- if (language === "en") {
603
- const excerpt = trimmedBody
604
- ? `\n\n## Narrative guidance excerpt\n\n${trimmedBody}\n`
605
- : "";
606
- return `# Book Rules (compat pointer — deprecated)\n\n> This file is kept for external readers only. The authoritative YAML frontmatter (protagonist / prohibitions / genreLock / ...) now lives at the top of outline/story_frame.md. readBookRules() prefers that location and only falls back here for books initialized before Phase 5 cleanup #3.${excerpt}`;
607
- }
608
- const excerpt = trimmedBody
609
- ? `\n\n## 叙事指引摘录\n\n${trimmedBody}\n`
610
- : "";
611
- return `# 本书规则(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威 YAML frontmatter(protagonist / prohibitions / genreLock / ...)已迁移至 outline/story_frame.md 顶部。readBookRules() 优先读那里,只有 Phase 5 cleanup #3 之前的老书才会回退到本文件。${excerpt}`;
612
- }
613
681
  // -------------------------------------------------------------------------
614
682
  // File writing
615
683
  // -------------------------------------------------------------------------
@@ -663,14 +731,7 @@ You MUST emit all **5 SECTION blocks in order**: story_frame → volume_map →
663
731
  await Promise.all(writes);
664
732
  return;
665
733
  }
666
- // Cleanup #3: book_rules YAML frontmatter is now the authoritative
667
- // schema for structured fields (protagonist, prohibitions, …). We prepend
668
- // it to story_frame.md so readers have one canonical place to look.
669
- // book_rules.md becomes a compat shim.
670
- const { frontmatter: bookRulesFrontmatter, body: bookRulesBody } = extractYamlFrontmatter(output.bookRules);
671
- const storyFrame = bookRulesFrontmatter
672
- ? `${bookRulesFrontmatter}\n\n${storyFrameBody.trim()}\n`
673
- : storyFrameBody;
734
+ const storyFrame = storyFrameBody.trim();
674
735
  // Phase 5 primary prose files
675
736
  writes.push(writeFile(join(outlineDir, "story_frame.md"), storyFrame, "utf-8"));
676
737
  writes.push(writeFile(join(outlineDir, "volume_map.md"), volumeMap, "utf-8"));
@@ -700,10 +761,7 @@ You MUST emit all **5 SECTION blocks in order**: story_frame → volume_map →
700
761
  // through readVolumeMap() in utils/outline-paths.ts, which prefers
701
762
  // outline/volume_map.md and falls back to legacy volume_outline.md for
702
763
  // books initialized before Phase 5.
703
- // book_rules.md is now a compat shim — the authoritative YAML
704
- // frontmatter lives on story_frame.md (cleanup #3). readBookRules()
705
- // prefers story_frame.md but still falls back here for older books.
706
- writes.push(writeFile(join(storyDir, "book_rules.md"), this.buildBookRulesShim(bookRulesBody, language), "utf-8"));
764
+ writes.push(writeFile(join(storyDir, "book_rules.md"), output.bookRules.trim() + "\n", "utf-8"));
707
765
  // Runtime state files.
708
766
  // Phase 5 consolidation: the architect no longer emits a current_state
709
767
  // section (only 3 genres — 港综同人/年代文/都市重生 — benefit from a
@@ -816,7 +874,7 @@ ${continuationDirective}
816
874
  { role: "system", content: systemPrompt },
817
875
  { role: "user", content: userMessage },
818
876
  ], { temperature: 0.5 });
819
- return this.parseSections(response.content, resolvedLanguage);
877
+ return this.parseSectionsWithRepair(response.content, resolvedLanguage);
820
878
  }
821
879
  async generateFanficFoundation(book, fanficCanon, fanficMode, reviewFeedback) {
822
880
  const { profile: gp, body: genreBody } = await readGenreProfile(this.ctx.projectRoot, book.genre);
@@ -851,8 +909,8 @@ ${genreBody}
851
909
 
852
910
  - 主要角色必须来自原作正典
853
911
  - 可添加原创配角,标注"原创"
854
- - book_rules fanficMode 必须设为 "${fanficMode}"
855
- - book_rules 只输出 YAML frontmatter,散文写进 story_frame.世界观底色
912
+ - book_rules 用普通 Markdown 规则卡;必须写清同人模式:${fanficMode}
913
+ - 长篇散文规则写进 story_frame.世界观底色,book_rules 只保留主角、题材锁、同人模式、禁止事项等可执行规则
856
914
  - 主角弧线只写在 roles/主要角色/<主角>.md,不在 story_frame 重复
857
915
  - 所有 outline 必须是散文密度`;
858
916
  const response = await this.chat([
@@ -862,7 +920,7 @@ ${genreBody}
862
920
  content: `请为标题为"${book.title}"的${fanficMode}模式同人小说生成基础设定。目标${book.targetChapters}章,每章${book.chapterWordCount}字。`,
863
921
  },
864
922
  ], { temperature: 0.7 });
865
- return this.parseSections(response.content, book.language ?? "zh");
923
+ return this.parseSectionsWithRepair(response.content, book.language ?? "zh");
866
924
  }
867
925
  // -------------------------------------------------------------------------
868
926
  // Helpers
@@ -890,6 +948,66 @@ ${trimmed}\n`;
890
948
  .replace(/[^a-z0-9]+/g, "_")
891
949
  .replace(/^_+|_+$/g, "");
892
950
  }
951
+ canonicalSectionNameFromHeading(heading) {
952
+ const normalized = this.normalizeSectionName(heading);
953
+ if ([
954
+ "story_frame",
955
+ "story_bible",
956
+ "story_foundation",
957
+ "foundation",
958
+ ].some((name) => normalized.includes(name))
959
+ || /(故事框架|故事圣经|基础设定|世界框架|故事底座)/.test(heading)) {
960
+ return "story_frame";
961
+ }
962
+ if ([
963
+ "volume_map",
964
+ "volume_outline",
965
+ "outline",
966
+ "plot_map",
967
+ ].some((name) => normalized.includes(name))
968
+ || /(分卷地图|卷纲|分卷大纲|章节地图|故事大纲)/.test(heading)) {
969
+ return "volume_map";
970
+ }
971
+ if ([
972
+ "roles",
973
+ "characters",
974
+ "character_cards",
975
+ ].some((name) => normalized.includes(name))
976
+ || /(角色设定|人物设定|角色卡|主要角色|角色|人物)/.test(heading)) {
977
+ return "roles";
978
+ }
979
+ if ([
980
+ "book_rules",
981
+ "rules",
982
+ "writing_rules",
983
+ ].some((name) => normalized.includes(name))
984
+ || /(本书规则|写作规则|运行规则|创作规则|规则卡)/.test(heading)) {
985
+ return "book_rules";
986
+ }
987
+ if ([
988
+ "pending_hooks",
989
+ "hooks",
990
+ "hook_ledger",
991
+ ].some((name) => normalized.includes(name))
992
+ || /(待回收钩子|待回收伏笔|伏笔表|钩子表|钩子|伏笔)/.test(heading)) {
993
+ return "pending_hooks";
994
+ }
995
+ if ([
996
+ "rhythm_principles",
997
+ "rhythm",
998
+ ].some((name) => normalized.includes(name))
999
+ || /(节奏原则|节奏)/.test(heading)) {
1000
+ return "rhythm_principles";
1001
+ }
1002
+ if ([
1003
+ "current_state",
1004
+ "initial_state",
1005
+ ].some((name) => normalized.includes(name))
1006
+ || /(当前状态|初始状态)/.test(heading)) {
1007
+ return "current_state";
1008
+ }
1009
+ return null;
1010
+ }
893
1011
  stripTrailingAssistantCoda(section) {
894
1012
  const lines = section.split("\n");
895
1013
  const cutoff = lines.findIndex((line) => {
@@ -963,7 +1081,10 @@ ${trimmed}\n`;
963
1081
  };
964
1082
  const promotedHooks = normalizedHooks.map((hook) => {
965
1083
  const decision = shouldPromoteHook(hook, promotionContext);
966
- return { ...hook, promoted: decision.promote };
1084
+ const status = !decision.promote && hook.lastAdvancedChapter <= 0
1085
+ ? this.normalizeDormantSeedStatus(hook.status, language)
1086
+ : hook.status;
1087
+ return { ...hook, status, promoted: decision.promote };
967
1088
  });
968
1089
  return renderHookSnapshot(promotedHooks, language);
969
1090
  }
@@ -998,6 +1119,13 @@ ${trimmed}\n`;
998
1119
  }
999
1120
  return volumes;
1000
1121
  }
1122
+ normalizeDormantSeedStatus(status, language) {
1123
+ const normalized = status?.trim().toLowerCase() ?? "";
1124
+ if (!normalized || /^(open|opened|active)$/i.test(normalized)) {
1125
+ return language === "zh" ? "暂缓" : "deferred";
1126
+ }
1127
+ return status?.trim() || (language === "zh" ? "暂缓" : "deferred");
1128
+ }
1001
1129
  parseHookChapterNumber(value) {
1002
1130
  if (!value)
1003
1131
  return 0;