@actalk/inkos-core 1.3.6 → 1.3.7-canary.36.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 (444) hide show
  1. package/dist/agent/agent-session.d.ts +7 -7
  2. package/dist/agent/agent-session.d.ts.map +1 -1
  3. package/dist/agent/agent-session.js +392 -63
  4. package/dist/agent/agent-session.js.map +1 -1
  5. package/dist/agent/agent-system-prompt.d.ts.map +1 -1
  6. package/dist/agent/agent-system-prompt.js +20 -16
  7. package/dist/agent/agent-system-prompt.js.map +1 -1
  8. package/dist/agent/agent-tools.d.ts +4 -1
  9. package/dist/agent/agent-tools.d.ts.map +1 -1
  10. package/dist/agent/agent-tools.js +81 -64
  11. package/dist/agent/agent-tools.js.map +1 -1
  12. package/dist/agents/architect.d.ts +18 -20
  13. package/dist/agents/architect.d.ts.map +1 -1
  14. package/dist/agents/architect.js +513 -632
  15. package/dist/agents/architect.js.map +1 -1
  16. package/dist/agents/chapter-analyzer.d.ts.map +1 -1
  17. package/dist/agents/chapter-analyzer.js +10 -6
  18. package/dist/agents/chapter-analyzer.js.map +1 -1
  19. package/dist/agents/composer.d.ts +1 -13
  20. package/dist/agents/composer.d.ts.map +1 -1
  21. package/dist/agents/composer.js +297 -290
  22. package/dist/agents/composer.js.map +1 -1
  23. package/dist/agents/consolidator.d.ts +17 -1
  24. package/dist/agents/consolidator.d.ts.map +1 -1
  25. package/dist/agents/consolidator.js +44 -6
  26. package/dist/agents/consolidator.js.map +1 -1
  27. package/dist/agents/continuity.d.ts +4 -1
  28. package/dist/agents/continuity.d.ts.map +1 -1
  29. package/dist/agents/continuity.js +111 -21
  30. package/dist/agents/continuity.js.map +1 -1
  31. package/dist/agents/length-normalizer.d.ts.map +1 -1
  32. package/dist/agents/length-normalizer.js +1 -4
  33. package/dist/agents/length-normalizer.js.map +1 -1
  34. package/dist/agents/planner-context.d.ts +54 -0
  35. package/dist/agents/planner-context.d.ts.map +1 -0
  36. package/dist/agents/planner-context.js +245 -0
  37. package/dist/agents/planner-context.js.map +1 -0
  38. package/dist/agents/planner-prompts.d.ts +36 -0
  39. package/dist/agents/planner-prompts.d.ts.map +1 -0
  40. package/dist/agents/planner-prompts.js +350 -0
  41. package/dist/agents/planner-prompts.js.map +1 -0
  42. package/dist/agents/planner.d.ts +39 -11
  43. package/dist/agents/planner.d.ts.map +1 -1
  44. package/dist/agents/planner.js +212 -195
  45. package/dist/agents/planner.js.map +1 -1
  46. package/dist/agents/polisher.d.ts +33 -0
  47. package/dist/agents/polisher.d.ts.map +1 -0
  48. package/dist/agents/polisher.js +122 -0
  49. package/dist/agents/polisher.js.map +1 -0
  50. package/dist/agents/post-write-validator.d.ts +1 -0
  51. package/dist/agents/post-write-validator.d.ts.map +1 -1
  52. package/dist/agents/post-write-validator.js +13 -0
  53. package/dist/agents/post-write-validator.js.map +1 -1
  54. package/dist/agents/reviser.d.ts +6 -2
  55. package/dist/agents/reviser.d.ts.map +1 -1
  56. package/dist/agents/reviser.js +379 -98
  57. package/dist/agents/reviser.js.map +1 -1
  58. package/dist/agents/rules-reader.d.ts +15 -2
  59. package/dist/agents/rules-reader.d.ts.map +1 -1
  60. package/dist/agents/rules-reader.js +49 -6
  61. package/dist/agents/rules-reader.js.map +1 -1
  62. package/dist/agents/state-validator.d.ts +9 -1
  63. package/dist/agents/state-validator.d.ts.map +1 -1
  64. package/dist/agents/state-validator.js +37 -1
  65. package/dist/agents/state-validator.js.map +1 -1
  66. package/dist/agents/writer-prompts.d.ts +1 -0
  67. package/dist/agents/writer-prompts.d.ts.map +1 -1
  68. package/dist/agents/writer-prompts.js +272 -29
  69. package/dist/agents/writer-prompts.js.map +1 -1
  70. package/dist/agents/writer.d.ts +12 -3
  71. package/dist/agents/writer.d.ts.map +1 -1
  72. package/dist/agents/writer.js +77 -107
  73. package/dist/agents/writer.js.map +1 -1
  74. package/dist/index.d.ts +20 -5
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +19 -5
  77. package/dist/index.js.map +1 -1
  78. package/dist/interaction/book-session-store.d.ts.map +1 -1
  79. package/dist/interaction/book-session-store.js +84 -69
  80. package/dist/interaction/book-session-store.js.map +1 -1
  81. package/dist/interaction/events.d.ts +2 -2
  82. package/dist/interaction/project-tools.d.ts +1 -0
  83. package/dist/interaction/project-tools.d.ts.map +1 -1
  84. package/dist/interaction/project-tools.js +51 -3
  85. package/dist/interaction/project-tools.js.map +1 -1
  86. package/dist/interaction/session-transcript-legacy.d.ts +4 -0
  87. package/dist/interaction/session-transcript-legacy.d.ts.map +1 -0
  88. package/dist/interaction/session-transcript-legacy.js +100 -0
  89. package/dist/interaction/session-transcript-legacy.js.map +1 -0
  90. package/dist/interaction/session-transcript-restore.d.ts +17 -0
  91. package/dist/interaction/session-transcript-restore.d.ts.map +1 -0
  92. package/dist/interaction/session-transcript-restore.js +493 -0
  93. package/dist/interaction/session-transcript-restore.js.map +1 -0
  94. package/dist/interaction/session-transcript-schema.d.ts +402 -0
  95. package/dist/interaction/session-transcript-schema.d.ts.map +1 -0
  96. package/dist/interaction/session-transcript-schema.js +59 -0
  97. package/dist/interaction/session-transcript-schema.js.map +1 -0
  98. package/dist/interaction/session-transcript.d.ts +14 -0
  99. package/dist/interaction/session-transcript.d.ts.map +1 -0
  100. package/dist/interaction/session-transcript.js +152 -0
  101. package/dist/interaction/session-transcript.js.map +1 -0
  102. package/dist/interaction/session.d.ts +10 -10
  103. package/dist/interaction/session.d.ts.map +1 -1
  104. package/dist/interaction/session.js +5 -3
  105. package/dist/interaction/session.js.map +1 -1
  106. package/dist/llm/provider.d.ts +6 -9
  107. package/dist/llm/provider.d.ts.map +1 -1
  108. package/dist/llm/provider.js +255 -63
  109. package/dist/llm/provider.js.map +1 -1
  110. package/dist/llm/providers/endpoints/ai360.d.ts +10 -0
  111. package/dist/llm/providers/endpoints/ai360.d.ts.map +1 -0
  112. package/dist/llm/providers/endpoints/ai360.js +34 -0
  113. package/dist/llm/providers/endpoints/ai360.js.map +1 -0
  114. package/dist/llm/providers/endpoints/anthropic.d.ts +12 -0
  115. package/dist/llm/providers/endpoints/anthropic.d.ts.map +1 -0
  116. package/dist/llm/providers/endpoints/anthropic.js +72 -0
  117. package/dist/llm/providers/endpoints/anthropic.js.map +1 -0
  118. package/dist/llm/providers/endpoints/astronCodingPlan.d.ts +16 -0
  119. package/dist/llm/providers/endpoints/astronCodingPlan.d.ts.map +1 -0
  120. package/dist/llm/providers/endpoints/astronCodingPlan.js +16 -0
  121. package/dist/llm/providers/endpoints/astronCodingPlan.js.map +1 -0
  122. package/dist/llm/providers/endpoints/baichuan.d.ts +10 -0
  123. package/dist/llm/providers/endpoints/baichuan.d.ts.map +1 -0
  124. package/dist/llm/providers/endpoints/baichuan.js +20 -0
  125. package/dist/llm/providers/endpoints/baichuan.js.map +1 -0
  126. package/dist/llm/providers/endpoints/bailian.d.ts +25 -0
  127. package/dist/llm/providers/endpoints/bailian.d.ts.map +1 -0
  128. package/dist/llm/providers/endpoints/bailian.js +42 -0
  129. package/dist/llm/providers/endpoints/bailian.js.map +1 -0
  130. package/dist/llm/providers/endpoints/bailianCodingPlan.d.ts +10 -0
  131. package/dist/llm/providers/endpoints/bailianCodingPlan.d.ts.map +1 -0
  132. package/dist/llm/providers/endpoints/bailianCodingPlan.js +22 -0
  133. package/dist/llm/providers/endpoints/bailianCodingPlan.js.map +1 -0
  134. package/dist/llm/providers/endpoints/custom.d.ts +16 -0
  135. package/dist/llm/providers/endpoints/custom.d.ts.map +1 -0
  136. package/dist/llm/providers/endpoints/custom.js +15 -0
  137. package/dist/llm/providers/endpoints/custom.js.map +1 -0
  138. package/dist/llm/providers/endpoints/deepseek.d.ts +17 -0
  139. package/dist/llm/providers/endpoints/deepseek.d.ts.map +1 -0
  140. package/dist/llm/providers/endpoints/deepseek.js +20 -0
  141. package/dist/llm/providers/endpoints/deepseek.js.map +1 -0
  142. package/dist/llm/providers/endpoints/giteeai.d.ts +10 -0
  143. package/dist/llm/providers/endpoints/giteeai.d.ts.map +1 -0
  144. package/dist/llm/providers/endpoints/giteeai.js +33 -0
  145. package/dist/llm/providers/endpoints/giteeai.js.map +1 -0
  146. package/dist/llm/providers/endpoints/githubCopilot.d.ts +10 -0
  147. package/dist/llm/providers/endpoints/githubCopilot.d.ts.map +1 -0
  148. package/dist/llm/providers/endpoints/githubCopilot.js +35 -0
  149. package/dist/llm/providers/endpoints/githubCopilot.js.map +1 -0
  150. package/dist/llm/providers/endpoints/glmCodingPlan.d.ts +9 -0
  151. package/dist/llm/providers/endpoints/glmCodingPlan.d.ts.map +1 -0
  152. package/dist/llm/providers/endpoints/glmCodingPlan.js +21 -0
  153. package/dist/llm/providers/endpoints/glmCodingPlan.js.map +1 -0
  154. package/dist/llm/providers/endpoints/google.d.ts +12 -0
  155. package/dist/llm/providers/endpoints/google.d.ts.map +1 -0
  156. package/dist/llm/providers/endpoints/google.js +41 -0
  157. package/dist/llm/providers/endpoints/google.js.map +1 -0
  158. package/dist/llm/providers/endpoints/hunyuan.d.ts +10 -0
  159. package/dist/llm/providers/endpoints/hunyuan.d.ts.map +1 -0
  160. package/dist/llm/providers/endpoints/hunyuan.js +34 -0
  161. package/dist/llm/providers/endpoints/hunyuan.js.map +1 -0
  162. package/dist/llm/providers/endpoints/infiniai.d.ts +10 -0
  163. package/dist/llm/providers/endpoints/infiniai.d.ts.map +1 -0
  164. package/dist/llm/providers/endpoints/infiniai.js +64 -0
  165. package/dist/llm/providers/endpoints/infiniai.js.map +1 -0
  166. package/dist/llm/providers/endpoints/internlm.d.ts +10 -0
  167. package/dist/llm/providers/endpoints/internlm.d.ts.map +1 -0
  168. package/dist/llm/providers/endpoints/internlm.js +20 -0
  169. package/dist/llm/providers/endpoints/internlm.js.map +1 -0
  170. package/dist/llm/providers/endpoints/kimiCodingPlan.d.ts +9 -0
  171. package/dist/llm/providers/endpoints/kimiCodingPlan.d.ts.map +1 -0
  172. package/dist/llm/providers/endpoints/kimiCodingPlan.js +17 -0
  173. package/dist/llm/providers/endpoints/kimiCodingPlan.js.map +1 -0
  174. package/dist/llm/providers/endpoints/longcat.d.ts +9 -0
  175. package/dist/llm/providers/endpoints/longcat.d.ts.map +1 -0
  176. package/dist/llm/providers/endpoints/longcat.js +18 -0
  177. package/dist/llm/providers/endpoints/longcat.js.map +1 -0
  178. package/dist/llm/providers/endpoints/minimax.d.ts +15 -0
  179. package/dist/llm/providers/endpoints/minimax.d.ts.map +1 -0
  180. package/dist/llm/providers/endpoints/minimax.js +26 -0
  181. package/dist/llm/providers/endpoints/minimax.js.map +1 -0
  182. package/dist/llm/providers/endpoints/minimaxCodingPlan.d.ts +9 -0
  183. package/dist/llm/providers/endpoints/minimaxCodingPlan.d.ts.map +1 -0
  184. package/dist/llm/providers/endpoints/minimaxCodingPlan.js +21 -0
  185. package/dist/llm/providers/endpoints/minimaxCodingPlan.js.map +1 -0
  186. package/dist/llm/providers/endpoints/mistral.d.ts +11 -0
  187. package/dist/llm/providers/endpoints/mistral.d.ts.map +1 -0
  188. package/dist/llm/providers/endpoints/mistral.js +31 -0
  189. package/dist/llm/providers/endpoints/mistral.js.map +1 -0
  190. package/dist/llm/providers/endpoints/modelscope.d.ts +10 -0
  191. package/dist/llm/providers/endpoints/modelscope.d.ts.map +1 -0
  192. package/dist/llm/providers/endpoints/modelscope.js +22 -0
  193. package/dist/llm/providers/endpoints/modelscope.js.map +1 -0
  194. package/dist/llm/providers/endpoints/moonshot.d.ts +12 -0
  195. package/dist/llm/providers/endpoints/moonshot.d.ts.map +1 -0
  196. package/dist/llm/providers/endpoints/moonshot.js +29 -0
  197. package/dist/llm/providers/endpoints/moonshot.js.map +1 -0
  198. package/dist/llm/providers/endpoints/newapi.d.ts +14 -0
  199. package/dist/llm/providers/endpoints/newapi.d.ts.map +1 -0
  200. package/dist/llm/providers/endpoints/newapi.js +14 -0
  201. package/dist/llm/providers/endpoints/newapi.js.map +1 -0
  202. package/dist/llm/providers/endpoints/ollama.d.ts +12 -0
  203. package/dist/llm/providers/endpoints/ollama.d.ts.map +1 -0
  204. package/dist/llm/providers/endpoints/ollama.js +63 -0
  205. package/dist/llm/providers/endpoints/ollama.js.map +1 -0
  206. package/dist/llm/providers/endpoints/openai.d.ts +12 -0
  207. package/dist/llm/providers/endpoints/openai.d.ts.map +1 -0
  208. package/dist/llm/providers/endpoints/openai.js +67 -0
  209. package/dist/llm/providers/endpoints/openai.js.map +1 -0
  210. package/dist/llm/providers/endpoints/opencodeCodingPlan.d.ts +9 -0
  211. package/dist/llm/providers/endpoints/opencodeCodingPlan.d.ts.map +1 -0
  212. package/dist/llm/providers/endpoints/opencodeCodingPlan.js +23 -0
  213. package/dist/llm/providers/endpoints/opencodeCodingPlan.js.map +1 -0
  214. package/dist/llm/providers/endpoints/openrouter.d.ts +15 -0
  215. package/dist/llm/providers/endpoints/openrouter.d.ts.map +1 -0
  216. package/dist/llm/providers/endpoints/openrouter.js +74 -0
  217. package/dist/llm/providers/endpoints/openrouter.js.map +1 -0
  218. package/dist/llm/providers/endpoints/ppio.d.ts +15 -0
  219. package/dist/llm/providers/endpoints/ppio.d.ts.map +1 -0
  220. package/dist/llm/providers/endpoints/ppio.js +73 -0
  221. package/dist/llm/providers/endpoints/ppio.js.map +1 -0
  222. package/dist/llm/providers/endpoints/qiniu.d.ts +10 -0
  223. package/dist/llm/providers/endpoints/qiniu.d.ts.map +1 -0
  224. package/dist/llm/providers/endpoints/qiniu.js +24 -0
  225. package/dist/llm/providers/endpoints/qiniu.js.map +1 -0
  226. package/dist/llm/providers/endpoints/sensenova.d.ts +10 -0
  227. package/dist/llm/providers/endpoints/sensenova.d.ts.map +1 -0
  228. package/dist/llm/providers/endpoints/sensenova.js +37 -0
  229. package/dist/llm/providers/endpoints/sensenova.js.map +1 -0
  230. package/dist/llm/providers/endpoints/siliconcloud.d.ts +14 -0
  231. package/dist/llm/providers/endpoints/siliconcloud.d.ts.map +1 -0
  232. package/dist/llm/providers/endpoints/siliconcloud.js +114 -0
  233. package/dist/llm/providers/endpoints/siliconcloud.js.map +1 -0
  234. package/dist/llm/providers/endpoints/spark.d.ts +14 -0
  235. package/dist/llm/providers/endpoints/spark.d.ts.map +1 -0
  236. package/dist/llm/providers/endpoints/spark.js +21 -0
  237. package/dist/llm/providers/endpoints/spark.js.map +1 -0
  238. package/dist/llm/providers/endpoints/stepfun.d.ts +10 -0
  239. package/dist/llm/providers/endpoints/stepfun.d.ts.map +1 -0
  240. package/dist/llm/providers/endpoints/stepfun.js +27 -0
  241. package/dist/llm/providers/endpoints/stepfun.js.map +1 -0
  242. package/dist/llm/providers/endpoints/tencentcloud.d.ts +10 -0
  243. package/dist/llm/providers/endpoints/tencentcloud.d.ts.map +1 -0
  244. package/dist/llm/providers/endpoints/tencentcloud.js +17 -0
  245. package/dist/llm/providers/endpoints/tencentcloud.js.map +1 -0
  246. package/dist/llm/providers/endpoints/volcengine.d.ts +10 -0
  247. package/dist/llm/providers/endpoints/volcengine.d.ts.map +1 -0
  248. package/dist/llm/providers/endpoints/volcengine.js +44 -0
  249. package/dist/llm/providers/endpoints/volcengine.js.map +1 -0
  250. package/dist/llm/providers/endpoints/volcengineCodingPlan.d.ts +19 -0
  251. package/dist/llm/providers/endpoints/volcengineCodingPlan.d.ts.map +1 -0
  252. package/dist/llm/providers/endpoints/volcengineCodingPlan.js +25 -0
  253. package/dist/llm/providers/endpoints/volcengineCodingPlan.js.map +1 -0
  254. package/dist/llm/providers/endpoints/wenxin.d.ts +10 -0
  255. package/dist/llm/providers/endpoints/wenxin.d.ts.map +1 -0
  256. package/dist/llm/providers/endpoints/wenxin.js +98 -0
  257. package/dist/llm/providers/endpoints/wenxin.js.map +1 -0
  258. package/dist/llm/providers/endpoints/xai.d.ts +11 -0
  259. package/dist/llm/providers/endpoints/xai.d.ts.map +1 -0
  260. package/dist/llm/providers/endpoints/xai.js +25 -0
  261. package/dist/llm/providers/endpoints/xai.js.map +1 -0
  262. package/dist/llm/providers/endpoints/xiaomimimo.d.ts +12 -0
  263. package/dist/llm/providers/endpoints/xiaomimimo.d.ts.map +1 -0
  264. package/dist/llm/providers/endpoints/xiaomimimo.js +16 -0
  265. package/dist/llm/providers/endpoints/xiaomimimo.js.map +1 -0
  266. package/dist/llm/providers/endpoints/zeroone.d.ts +10 -0
  267. package/dist/llm/providers/endpoints/zeroone.d.ts.map +1 -0
  268. package/dist/llm/providers/endpoints/zeroone.js +26 -0
  269. package/dist/llm/providers/endpoints/zeroone.js.map +1 -0
  270. package/dist/llm/providers/endpoints/zhipu.d.ts +12 -0
  271. package/dist/llm/providers/endpoints/zhipu.d.ts.map +1 -0
  272. package/dist/llm/providers/endpoints/zhipu.js +51 -0
  273. package/dist/llm/providers/endpoints/zhipu.js.map +1 -0
  274. package/dist/llm/providers/index.d.ts +5 -0
  275. package/dist/llm/providers/index.d.ts.map +1 -0
  276. package/dist/llm/providers/index.js +67 -0
  277. package/dist/llm/providers/index.js.map +1 -0
  278. package/dist/llm/providers/lookup.d.ts +16 -0
  279. package/dist/llm/providers/lookup.d.ts.map +1 -0
  280. package/dist/llm/providers/lookup.js +68 -0
  281. package/dist/llm/providers/lookup.js.map +1 -0
  282. package/dist/llm/providers/probe.d.ts +11 -0
  283. package/dist/llm/providers/probe.d.ts.map +1 -0
  284. package/dist/llm/providers/probe.js +24 -0
  285. package/dist/llm/providers/probe.js.map +1 -0
  286. package/dist/llm/providers/types.d.ts +71 -0
  287. package/dist/llm/providers/types.d.ts.map +1 -0
  288. package/dist/llm/providers/types.js +9 -0
  289. package/dist/llm/providers/types.js.map +1 -0
  290. package/dist/llm/providers/verify.d.ts +27 -0
  291. package/dist/llm/providers/verify.d.ts.map +1 -0
  292. package/dist/llm/providers/verify.js +77 -0
  293. package/dist/llm/providers/verify.js.map +1 -0
  294. package/dist/llm/secrets.d.ts.map +1 -1
  295. package/dist/llm/secrets.js +27 -2
  296. package/dist/llm/secrets.js.map +1 -1
  297. package/dist/llm/service-presets.d.ts +13 -3
  298. package/dist/llm/service-presets.d.ts.map +1 -1
  299. package/dist/llm/service-presets.js +78 -42
  300. package/dist/llm/service-presets.js.map +1 -1
  301. package/dist/llm/service-resolver.d.ts +1 -1
  302. package/dist/llm/service-resolver.d.ts.map +1 -1
  303. package/dist/llm/service-resolver.js +13 -0
  304. package/dist/llm/service-resolver.js.map +1 -1
  305. package/dist/models/book-rules.d.ts +23 -1
  306. package/dist/models/book-rules.d.ts.map +1 -1
  307. package/dist/models/book-rules.js +54 -2
  308. package/dist/models/book-rules.js.map +1 -1
  309. package/dist/models/input-governance.d.ts +20 -244
  310. package/dist/models/input-governance.d.ts.map +1 -1
  311. package/dist/models/input-governance.js +7 -51
  312. package/dist/models/input-governance.js.map +1 -1
  313. package/dist/models/project.d.ts +29 -28
  314. package/dist/models/project.d.ts.map +1 -1
  315. package/dist/models/project.js +11 -9
  316. package/dist/models/project.js.map +1 -1
  317. package/dist/models/runtime-state.d.ts +120 -0
  318. package/dist/models/runtime-state.d.ts.map +1 -1
  319. package/dist/models/runtime-state.js +12 -0
  320. package/dist/models/runtime-state.js.map +1 -1
  321. package/dist/pipeline/agent.d.ts.map +1 -1
  322. package/dist/pipeline/agent.js +59 -10
  323. package/dist/pipeline/agent.js.map +1 -1
  324. package/dist/pipeline/chapter-review-cycle.d.ts +11 -4
  325. package/dist/pipeline/chapter-review-cycle.d.ts.map +1 -1
  326. package/dist/pipeline/chapter-review-cycle.js +156 -73
  327. package/dist/pipeline/chapter-review-cycle.js.map +1 -1
  328. package/dist/pipeline/chapter-truth-validation.d.ts +2 -1
  329. package/dist/pipeline/chapter-truth-validation.d.ts.map +1 -1
  330. package/dist/pipeline/chapter-truth-validation.js +1 -1
  331. package/dist/pipeline/chapter-truth-validation.js.map +1 -1
  332. package/dist/pipeline/persisted-governed-plan.d.ts +1 -0
  333. package/dist/pipeline/persisted-governed-plan.d.ts.map +1 -1
  334. package/dist/pipeline/persisted-governed-plan.js +181 -66
  335. package/dist/pipeline/persisted-governed-plan.js.map +1 -1
  336. package/dist/pipeline/runner.d.ts +20 -17
  337. package/dist/pipeline/runner.d.ts.map +1 -1
  338. package/dist/pipeline/runner.js +359 -84
  339. package/dist/pipeline/runner.js.map +1 -1
  340. package/dist/state/manager.d.ts.map +1 -1
  341. package/dist/state/manager.js +44 -4
  342. package/dist/state/manager.js.map +1 -1
  343. package/dist/state/memory-db.d.ts +6 -0
  344. package/dist/state/memory-db.d.ts.map +1 -1
  345. package/dist/state/memory-db.js.map +1 -1
  346. package/dist/state/runtime-state-store.d.ts.map +1 -1
  347. package/dist/state/runtime-state-store.js +4 -1
  348. package/dist/state/runtime-state-store.js.map +1 -1
  349. package/dist/state/state-projections.d.ts +3 -1
  350. package/dist/state/state-projections.d.ts.map +1 -1
  351. package/dist/state/state-projections.js +58 -15
  352. package/dist/state/state-projections.js.map +1 -1
  353. package/dist/utils/book-id.d.ts +4 -0
  354. package/dist/utils/book-id.d.ts.map +1 -0
  355. package/dist/utils/book-id.js +27 -0
  356. package/dist/utils/book-id.js.map +1 -0
  357. package/dist/utils/chapter-cadence.js +1 -0
  358. package/dist/utils/chapter-cadence.js.map +1 -1
  359. package/dist/utils/chapter-memo-parser.d.ts +19 -0
  360. package/dist/utils/chapter-memo-parser.d.ts.map +1 -0
  361. package/dist/utils/chapter-memo-parser.js +114 -0
  362. package/dist/utils/chapter-memo-parser.js.map +1 -0
  363. package/dist/utils/config-loader.d.ts +7 -11
  364. package/dist/utils/config-loader.d.ts.map +1 -1
  365. package/dist/utils/config-loader.js +13 -253
  366. package/dist/utils/config-loader.js.map +1 -1
  367. package/dist/utils/context-assembly.d.ts +24 -0
  368. package/dist/utils/context-assembly.d.ts.map +1 -0
  369. package/dist/utils/context-assembly.js +77 -0
  370. package/dist/utils/context-assembly.js.map +1 -0
  371. package/dist/utils/effective-llm-config.d.ts +34 -0
  372. package/dist/utils/effective-llm-config.d.ts.map +1 -0
  373. package/dist/utils/effective-llm-config.js +417 -0
  374. package/dist/utils/effective-llm-config.js.map +1 -0
  375. package/dist/utils/governed-working-set.js +1 -1
  376. package/dist/utils/governed-working-set.js.map +1 -1
  377. package/dist/utils/hook-ledger-validator.d.ts +82 -0
  378. package/dist/utils/hook-ledger-validator.d.ts.map +1 -0
  379. package/dist/utils/hook-ledger-validator.js +225 -0
  380. package/dist/utils/hook-ledger-validator.js.map +1 -0
  381. package/dist/utils/hook-lifecycle.d.ts +5 -0
  382. package/dist/utils/hook-lifecycle.d.ts.map +1 -1
  383. package/dist/utils/hook-lifecycle.js +32 -0
  384. package/dist/utils/hook-lifecycle.js.map +1 -1
  385. package/dist/utils/hook-policy.d.ts +0 -17
  386. package/dist/utils/hook-policy.d.ts.map +1 -1
  387. package/dist/utils/hook-policy.js +0 -30
  388. package/dist/utils/hook-policy.js.map +1 -1
  389. package/dist/utils/hook-promotion.d.ts +83 -0
  390. package/dist/utils/hook-promotion.d.ts.map +1 -0
  391. package/dist/utils/hook-promotion.js +241 -0
  392. package/dist/utils/hook-promotion.js.map +1 -0
  393. package/dist/utils/hook-stale-detection.d.ts +51 -0
  394. package/dist/utils/hook-stale-detection.d.ts.map +1 -0
  395. package/dist/utils/hook-stale-detection.js +125 -0
  396. package/dist/utils/hook-stale-detection.js.map +1 -0
  397. package/dist/utils/llm-endpoint-auth.d.ts +5 -0
  398. package/dist/utils/llm-endpoint-auth.d.ts.map +1 -0
  399. package/dist/utils/llm-endpoint-auth.js +36 -0
  400. package/dist/utils/llm-endpoint-auth.js.map +1 -0
  401. package/dist/utils/llm-env.d.ts +14 -0
  402. package/dist/utils/llm-env.d.ts.map +1 -0
  403. package/dist/utils/llm-env.js +53 -0
  404. package/dist/utils/llm-env.js.map +1 -0
  405. package/dist/utils/memory-retrieval.d.ts +23 -1
  406. package/dist/utils/memory-retrieval.d.ts.map +1 -1
  407. package/dist/utils/memory-retrieval.js +45 -2
  408. package/dist/utils/memory-retrieval.js.map +1 -1
  409. package/dist/utils/narrative-control.d.ts +16 -0
  410. package/dist/utils/narrative-control.d.ts.map +1 -0
  411. package/dist/utils/narrative-control.js +131 -0
  412. package/dist/utils/narrative-control.js.map +1 -0
  413. package/dist/utils/path-safety.d.ts +2 -0
  414. package/dist/utils/path-safety.d.ts.map +1 -0
  415. package/dist/utils/path-safety.js +11 -0
  416. package/dist/utils/path-safety.js.map +1 -0
  417. package/dist/utils/planning-materials.d.ts +35 -0
  418. package/dist/utils/planning-materials.d.ts.map +1 -0
  419. package/dist/utils/planning-materials.js +123 -0
  420. package/dist/utils/planning-materials.js.map +1 -0
  421. package/dist/utils/proxy-fetch.d.ts +9 -0
  422. package/dist/utils/proxy-fetch.d.ts.map +1 -0
  423. package/dist/utils/proxy-fetch.js +31 -0
  424. package/dist/utils/proxy-fetch.js.map +1 -0
  425. package/dist/utils/runtime-writer.d.ts +14 -0
  426. package/dist/utils/runtime-writer.d.ts.map +1 -0
  427. package/dist/utils/runtime-writer.js +21 -0
  428. package/dist/utils/runtime-writer.js.map +1 -0
  429. package/dist/utils/spot-fix-patches.d.ts +7 -0
  430. package/dist/utils/spot-fix-patches.d.ts.map +1 -1
  431. package/dist/utils/spot-fix-patches.js +109 -36
  432. package/dist/utils/spot-fix-patches.js.map +1 -1
  433. package/dist/utils/story-markdown.d.ts.map +1 -1
  434. package/dist/utils/story-markdown.js +104 -6
  435. package/dist/utils/story-markdown.js.map +1 -1
  436. package/dist/utils/writing-methodology.d.ts +10 -0
  437. package/dist/utils/writing-methodology.d.ts.map +1 -0
  438. package/dist/utils/writing-methodology.js +163 -0
  439. package/dist/utils/writing-methodology.js.map +1 -0
  440. package/package.json +3 -2
  441. package/dist/utils/hook-agenda.d.ts +0 -21
  442. package/dist/utils/hook-agenda.d.ts.map +0 -1
  443. package/dist/utils/hook-agenda.js +0 -95
  444. package/dist/utils/hook-agenda.js.map +0 -1
@@ -3,7 +3,13 @@ import { readGenreProfile } from "./rules-reader.js";
3
3
  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
- /** markdown 的首部 YAML frontmatter 和正文。没有 frontmatter 时返回 frontmatter: null。 */
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
+ */
7
13
  function extractYamlFrontmatter(raw) {
8
14
  if (!raw)
9
15
  return { frontmatter: null, body: "" };
@@ -32,19 +38,13 @@ export class ArchitectAgent extends BaseAgent {
32
38
  ? this.buildRevisePrompt(options.reviseFrom)
33
39
  : "";
34
40
  const numericalBlock = gp.numericalSystem
35
- ? `- 有明确的数值/资源体系可追踪
36
- - 在 book_rules 中定义 numericalSystemOverrides(hardCap、resourceTypes)`
41
+ ? "- 有明确的数值/资源体系可追踪\n- 在 book_rules 中定义 numericalSystemOverrides(hardCap、resourceTypes)"
37
42
  : "- 本题材无数值系统,不需要资源账本";
38
- const powerBlock = gp.powerScaling
39
- ? "- 有明确的战力等级体系"
40
- : "";
41
- const eraBlock = gp.eraResearch
42
- ? "- 需要年代考据支撑(在 book_rules 中设置 eraConstraints)"
43
- : "";
44
- const basePrompt = resolvedLanguage === "en"
43
+ const powerBlock = gp.powerScaling ? "- 有明确的战力等级体系" : "";
44
+ const eraBlock = gp.eraResearch ? "- 需要年代考据支撑(在 book_rules 中设置 eraConstraints)" : "";
45
+ const systemPrompt = resolvedLanguage === "en"
45
46
  ? this.buildEnglishFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock)
46
47
  : this.buildChineseFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock);
47
- const systemPrompt = revisePrompt + basePrompt;
48
48
  const langPrefix = resolvedLanguage === "en"
49
49
  ? `【LANGUAGE OVERRIDE】ALL output (story_frame, volume_map, roles, book_rules, pending_hooks) MUST be written in English. Character names, place names, and all prose must be in English. The === SECTION: === tags remain unchanged. Do NOT emit rhythm_principles or current_state sections — rhythm principles live inside the last paragraph of volume_map; environment/era anchors (when relevant) are woven into story_frame's world-tonal-ground paragraph.\n\n`
50
50
  : "";
@@ -52,52 +52,46 @@ export class ArchitectAgent extends BaseAgent {
52
52
  ? `Generate the complete foundation for a ${gp.name} novel titled "${book.title}". Write everything in English.`
53
53
  : `请为标题为"${book.title}"的${gp.name}小说生成完整基础设定。`;
54
54
  const response = await this.chat([
55
- { role: "system", content: langPrefix + systemPrompt },
55
+ { role: "system", content: langPrefix + systemPrompt + revisePrompt },
56
56
  { role: "user", content: userMessage },
57
- ], { temperature: 0.8, maxTokens: 16384 });
58
- return this.parseSections(response.content);
57
+ ], { temperature: 0.8 });
58
+ return this.parseSections(response.content, resolvedLanguage);
59
59
  }
60
60
  buildRevisePrompt(reviseFrom) {
61
- return `你在把一本已有书的架构稿从条目式升级成段落式架构稿 + 一人一卡的角色目录。
61
+ return `\n\n## 既有架构稿修订模式
62
+ 你在把一本已有书的架构稿从条目式升级为当前的段落式架构稿 + 一人一卡角色目录;如果它已经是 Phase 5 结构,则按用户反馈二次重写。
62
63
 
63
- 原书信息(条目式架构稿原文,这是权威内容,必须完整保留里面的世界观 / 角色 / 主线 / 伏笔):
64
+ 原书信息(这是权威内容,必须完整保留其中的世界观、角色、主线、伏笔和语气):
64
65
 
65
- 【story_bible.md 全文】
66
+ 【story_bible / story_frame 全文】
66
67
  ${reviseFrom.storyBible || "(无)"}
67
68
 
68
- 【volume_outline.md 全文】
69
+ 【volume_outline / volume_map 全文】
69
70
  ${reviseFrom.volumeOutline || "(无)"}
70
71
 
71
- 【book_rules.md 全文】
72
+ 【book_rules 全文】
72
73
  ${reviseFrom.bookRules || "(无)"}
73
74
 
74
- 【character_matrix.md 全文】
75
+ 【character_matrix / roles 全文】
75
76
  ${reviseFrom.characterMatrix || "(无)"}
76
77
 
77
78
  你的任务:
78
- 1. story_bible 的内容重新组织成 4 段段落式 story_frame.md(主题 / 核心冲突 / 世界观底色 / 终局方向)
79
- 2. volume_outline 的内容重新组织成 5 段 volume_map.md + 末尾 1 段节奏原则
80
- 3. character_matrix 里的每个角色拆成一份 roles/主要角色/<name>.md 或 roles/次要角色/<name>.md
81
-
82
- 严格约束:
83
- - 世界观设定、角色设定、主线走向、已埋下的伏笔一个字都不能丢
84
- - 如果原内容里有 bullet 点,把它们连成段落;不要只是把 bullet 转成句号分隔
85
- - 主次角色的判断依据:character_matrix 里已标注的如果有,沿用;没有的话,把在 volume_outline 里出现频次高、或者承担主线冲突的列为主要,其余为次要
86
- - 基调 / 语气不要改变
87
- - 如果原内容里留有空白或未展开项,保留为空,不要主动补全
79
+ 1. 把现有内容重新组织成当前 5 SECTION:story_frame / volume_map / roles / book_rules / pending_hooks
80
+ 2. story_frame 使用段落式世界观与核心冲突,不要退回条目表格
81
+ 3. volume_map 使用段落式卷/章级方向,并把节奏原则放进末段
82
+ 4. roles 必须按一人一卡输出,主要/次要角色判断沿用原内容,缺失才按主线重要性推断
83
+ 5. pending_hooks 必须保留原有未回收伏笔,不要因为重写架构稿而清空
84
+ 6. 不要改动已写章节的运行时事实,不要重置 current_state / pending_hooks 之外的运行时日志
88
85
 
89
86
  用户额外要求:
90
87
  ${reviseFrom.userFeedback || "(无)"}
91
-
92
- ---
93
-
94
88
  `;
95
89
  }
96
90
  // -------------------------------------------------------------------------
97
- // Phase 5 段落式 prompt — zh
91
+ // Prose prompt — zh (primary)
98
92
  // -------------------------------------------------------------------------
99
93
  buildChineseFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock) {
100
- return `你是这本书的总架构师。你的唯一输出是**段落密度的基础设定**——不是表格、不是 schema、不是条目化 bullet。这本书的"灵气"从你这里来。你的段落密度决定了后面 planner 能不能读出"稀疏 memo",writer 能不能写出活人,reviewer 能不能校准硬伤。${contextBlock}${reviewFeedbackBlock}
94
+ return `你是这本书的总架构师。你的唯一输出是**散文密度的基础设定**——不是表格、不是 schema、不是条目化 bullet。v6 以后这本书的"灵气"从哪里来?从你这里来。你的散文密度决定了后面 planner 能不能读出"稀疏 memo",writer 能不能写出活人,reviewer 能不能校准硬伤。${contextBlock}${reviewFeedbackBlock}
101
95
 
102
96
  ## 书籍元信息
103
97
  - 平台:${book.platform}
@@ -117,46 +111,64 @@ ${eraBlock}
117
111
  ## 输出结构(5 个 SECTION,严格按 === SECTION: === 分块,不要漏任何一块)
118
112
 
119
113
  ## 去重铁律(必读)
120
- 禁止在多段里重复同一事实。主角的完整角色线只写在 roles;世界铁律只写在 story_frame.世界观底色;节奏原则只写在 volume_map 最后一段;角色当前现状只写在 roles.当前现状;初始钩子只写在 pending_hooks(startChapter=0 行)。**如果本书是年代文/历史同人/都市重生等需要年份、季节、重大历史事件作为锚点的题材**,把环境/时代锚自然织进 story_frame.世界观底色("1985 年 7 月,非典刚过"这类);**修仙/玄幻/系统等没有真实年份的题材直接省略**,不要硬凑。如果一个段落写了另一段的内容,删掉。
114
+ 禁止在多段里重复同一事实。主角弧线只写在 roles;世界铁律只写在 story_frame.世界观底色;节奏原则只写在 volume_map 最后一段;角色当前现状只写在 roles.当前现状;初始钩子只写在 pending_hooks(startChapter=0 行)。**如果本书是年代文/历史同人/都市重生等需要年份、季节、重大历史事件作为锚点的题材**,把环境/时代锚自然织进 story_frame.世界观底色("1985 年 7 月,非典刚过"这类);**修仙/玄幻/系统等没有真实年份的题材直接省略**,不要硬凑。如果一个段落写了另一段的内容,删掉。
121
115
 
122
116
  ## 预算(超预算必删)
123
- - story_frame ≤ 3000
124
- - volume_map ≤ 5000
125
- - roles 总 ≤ 8000
126
- - book_rules ≤ 500 字(仅 YAML)
127
- - pending_hooks ≤ 2000
117
+ - story_frame ≤ 3000 chars
118
+ - volume_map ≤ 5000 chars
119
+ - roles 总 ≤ 8000 chars
120
+ - book_rules ≤ 500 chars(仅 YAML)
121
+ - pending_hooks ≤ 2000 chars
128
122
 
129
123
  === SECTION: story_frame ===
130
124
 
131
- 这是段落式骨架。**4 段**,每段约 600-900 字,不要写表格,不要写 bullet list,写成能被人读下去的段落。段落标题用 \`## \` 开头,段落内部是正经段落。**主角的完整角色线不写在本 section;它的权威来源是 roles/主要角色/<主角>.md。** 本段只需一句指针:"本书主角是 X,完整角色线详见 roles/主要角色/X.md"。
125
+ 这是散文骨架。**4 段**,每段约 600-900 字,不要写表格,不要写 bullet list,写成能被人读下去的段落。段落标题用 \`## \` 开头,段落内部是正经段落。**主角弧线不写在本 section;它的权威来源是 roles/主要角色/<主角>.md。** 本段只需一句指针:"本书主角是 X,完整弧线详见 roles/主要角色/X.md"。
132
126
 
133
127
  ### 段 1:主题与基调
134
- 写这本书到底讲的是什么——不是"讲主角如何从弱到强"这种空话,而是具体的命题("一个被时代按在泥里的人,如何选择不被改写"、"当所有人都在撒谎时,坚持记录真相要付出什么代价")。主题下面跟着基调——温情冷冽悲壮肃杀,哪一种?为什么是这种而不是另一种?结尾用一句话指向主角并引向 roles(例:"本书主角是林辞,完整角色线详见 roles/主要角色/林辞.md")。
128
+ 写这本书到底讲的是什么——不是"讲主角如何从弱到强"这种空话,而是具体的命题("一个被时代按在泥里的人,如何选择不被改写"、"当所有人都在撒谎时,坚持记录真相要付出什么代价")。主题下面跟着基调——温情冷冽悲壮肃杀,哪一种?为什么是这种而不是另一种?结尾用一句话指向主角并引向 roles(例:"本书主角是林辞,完整弧线详见 roles/主要角色/林辞.md")。
135
129
 
136
- ### 段 2:核心冲突与对手定性
130
+ ### 段 2:核心冲突、对手定性、前台/后台双层故事
137
131
  这本书的主要矛盾是什么?不是"正邪对抗",而是"因为 A 相信 X、B 相信 Y,所以他们一定会在某件事上对撞"。主要对手是谁(至少 2 个:一个显性对手 + 一个结构性对手/体制),他们的动机从哪里长出来。对手不是工具,对手有自己的逻辑。
138
132
 
133
+ **本段必须显式写出"前台故事 / 后台故事"两条线**(番茄老师弈青锋的"台前台后"分层法):
134
+ - **前台故事**:读者每章看得到的表层冲突(查案、打怪、升级、谈恋爱、搞事业等),每个卷/arc 有独立的显性目标和完结点
135
+ - **后台故事**:贯穿全书的暗线——藏在所有前台事件背后的那台"机器"(幕后黑手、阴谋、身世秘密、体制压迫、命运诅咒等),读者只能通过碎片拼出来,大结局时才整体兑现
136
+
137
+ 两条线必须有因果关联,不能是平行宇宙——每一段前台冲突的背后都应该能追溯到后台故事的某个齿轮在转。**如果只有前台没有后台,故事会散成"独立事件集",没有往前拉的引力;如果只有后台没有前台,故事会憋闷、看不到爽感**。本段用散文明确写出:本书前台是什么、后台是什么、两者怎么咬合。
138
+
139
139
  ### 段 3:世界观底色(铁律 + 质感 + 本书专属规则)
140
- 这个世界的运行规则是什么?3-5 条**不可违反的铁律**——以段落形式写出,不要 bullet。这个世界的质感是什么——湿的还是干的、快的还是慢的、噪的还是静的?给 writer 一个明确的感官锚。**这一段同时承担原先 book_rules 正文里写的"叙事视角 / 本书专属规则 / 核心冲突驱动"等内容**——全部合并到这里写一次就够,不要再去 book_rules 重复。
140
+ 这个世界的运行规则是什么?3-5 条**不可违反的铁律**——以 prose 写出,不要 bullet。这个世界的质感是什么——湿的还是干的、快的还是慢的、噪的还是静的?给 writer 一个明确的感官锚(这是原来 particle_ledger 承载的基调部分)。**这一段同时承担原先 book_rules 正文里写的"叙事视角 / 本书专属规则 / 核心冲突驱动" prose 内容**——全部合并到这里写一次就够,不要再去 book_rules 重复。
141
141
 
142
- ### 段 4:终局方向
142
+ ### 段 4:终局方向 + 全书 Objective(OKR 大纲的根)
143
143
  这本书最后一章大概是什么感觉——不是"主角登顶"、"大结局"这种套话,而是**最后一个镜头**大致长什么样。主角最后在哪、做什么、身边有谁、心里想什么。这是给全书所有后面的规划一个远方靶子。
144
144
 
145
+ **本段末尾必须明确写出全书 Objective 一句话**(番茄老师弈青锋的 OKR 递归大纲法):这本书讲完时,主角必须达成一个**可验证的终局状态**(例:"从一个杂役修士成为宗门长老并公开父辈冤案的真相"、"从黑户打工妹成为掌控三家皮草公司的老板娘并亲手送前夫进监狱")。不要写"变强"、"复仇"这类抽象词,要写**一个能被外部观察者判定"达成 / 未达成"的具体状态**。这个 Objective 是全书 OKR 递归大纲的根——下面 volume_map 的每一卷会分解出这个 O 对应的 Key Results。
146
+
145
147
  === SECTION: volume_map ===
146
148
 
147
- 这是分卷段落式地图,**5 段主体 + 1 段节奏原则尾段**。**关键要求:只写到卷级内容**——写清楚每卷的主题、情绪曲线、卷间钩子、角色阶段目标、卷尾不可逆事件。**禁止指定具体章号任务**(不要写"第 17 章让他回家"这种章级布局)。章级规划是 Phase 3 planner 的职责,架构师只搭骨架、不编章目。
149
+ 这是分卷散文地图,**5 段主体 + 1 段节奏原则尾段**。**关键要求:只写到卷级 prose**——写清楚每卷的主题、情绪曲线、卷间钩子、角色阶段目标、卷尾不可逆事件。**禁止指定具体章号任务**(不要写"第 17 章让他回家"这种章级布局)。章级规划是 Phase 3 planner 的职责,架构师只搭骨架、不编章目。
148
150
 
149
151
  ### 段 1:各卷主题与情绪曲线
150
152
  有几卷?每卷的主题一句话,每卷的情绪曲线一段(哪里压、哪里爽、哪里冷、哪里暖)。不要机械的"第一卷打小怪第二卷打大怪",写情绪的流动。
151
153
 
152
- ### 段 2:卷间钩子与回收承诺
153
- 第 1 卷埋什么钩子、在哪一卷回收;第 2 卷埋什么、在哪一卷回收。段落式写,不要表格。**只写卷级**(如"第 1 卷埋的身世之谜在第 3 卷回收"),不要写具体章号。
154
+ ### 段 2:卷间钩子与回收承诺(前台/后台双层都要覆盖)
155
+ 第 1 卷埋什么钩子、在哪一卷回收;第 2 卷埋什么、在哪一卷回收。散文写,不要表格。**只写卷级**(如"第 1 卷埋的身世之谜在第 3 卷回收"),不要写具体章号。
156
+
157
+ **钩子必须覆盖前台 + 后台两层**(对应 story_frame.段 2 建立的双层故事):
158
+ - 前台钩子:当前卷内 arc 层面的短期钩子(查案谜题、对手身份、资源争夺等),预期在 1-2 卷内回收
159
+ - 后台钩子:贯穿全书的主线钩子(幕后真相、身世、体制秘密等),预期在终卷前后回收,核心的 3-7 条属于 core_hook=true
160
+
161
+ **如果本段只写前台钩子、没有后台钩子暗桩,说明你漏了整本书的引力轴,必须补上。**
154
162
 
155
- ### 段 3:角色阶段性目标
156
- 主角在第 1 卷末要到什么状态?第 2 卷末?每一卷结束时主角的身份/关系/能力/心境应该是什么。次要角色的阶段性变化也要点到(师父在第 2 卷会死、对手在第 3 卷会黑化等)。写阶段性,不写完整角色线(完整角色线在 roles)。
163
+ ### 段 3:各卷 OKR(Objective + Key Results)
164
+ OKR 递归大纲法分解全书 Objective(story_frame.段 4 末尾定的根 O):每一卷都必须明确给出:
165
+ - **Objective(卷级目标)**:本卷结束时主角必须达成的**可验证状态**,一句话,与全书 Objective 逻辑递进相连(例:全书 O = "成为宗门长老并公开冤案";卷 1 O = "从杂役转入正式弟子籍并拿到第一份能指向真相的线索")
166
+ - **Key Results(3 条,可量化/可观察)**:支撑该 O 达成的三个关键子成果,每条必须是外部观察者能判定是否完成的状态变更(例 KR1 = "拿下药园执事位置"、KR2 = "与灵安峰结成稳定盟约"、KR3 = "发现父辈案卷的第一半页残片")。不要写"变强"、"成长"这类模糊 KR
167
+
168
+ 次要角色的阶段性变化也要点到(师父在第 2 卷会死、对手在第 3 卷会黑化等),写在 KR 条目下作为附注。写阶段性,不写完整弧线(完整弧线在 roles)。**每一卷 3 个 KR 是下游 planner 分解章节任务的直接依据——planner 拿到一卷的 3 个 KR 后,按每 3-5 章推进一个 KR 的节奏排章。**
157
169
 
158
170
  ### 段 4:卷尾必须发生的改变
159
- 每一卷最后一章必须发生什么不可逆的事——权力结构改变、关系破裂、秘密暴露、主角身份重定位。写段落,一卷一段。**只写"必须发生什么",不指定是第几章**。
171
+ 每一卷最后一章必须发生什么不可逆的事——权力结构改变、关系破裂、秘密暴露、主角身份重定位。写散文,一卷一段。**只写"必须发生什么",不指定是第几章**。
160
172
 
161
173
  ### 段 5:节奏原则(具体化 + 通用)
162
174
  **这是节奏原则的唯一归宿,不再有独立 rhythm_principles section。** 本段输出 6 条节奏原则。**至少 3 条必须具体化到本书**(例:"前 30 章每 5 章一个小爽点"),其余可保留通用原则(例:"拒绝机械降神"、"高潮前 3-5 章埋伏笔")。具体化 + 通用混合是合法的。反面例子:"节奏要张弛有度"(废话)。正面例子:"前 30 章每 5 章一个小爽点,且小爽点必须落在章末 300 字内"。6 条各写 2-3 句,覆盖(顺序不强制、可替换同权重议题):
@@ -167,20 +179,15 @@ ${eraBlock}
167
179
  5. 爽点节奏——爽点间距多少章一个?什么类型为主?(具体化优先)
168
180
  6. 情感节点递进——情感关系每多少章必须有一次实质推进?
169
181
 
170
- ### 黄金三章法则(前三章必须遵循)
171
- - 第1章:抛出核心冲突(主角立即面临困境/危机/选择),禁止大段背景灌输
172
- - 第2章:展示金手指/核心能力(主角如何应对第1章的困境),让读者看到爽点预期
173
- - 第3章:明确短期目标(主角确立第一个具体可达成的目标),给读者追读理由
174
-
175
182
  === SECTION: roles ===
176
183
 
177
- 一人一卡。**主角卡是本书角色线的唯一权威来源**——story_frame 不再写主角的完整角色线,writer/planner 都从这里读。用以下格式分隔:
184
+ 一人一卡 prose。**主角卡是本书角色弧线的唯一权威来源**——story_frame 不再写主角弧线,writer/planner 都从这里读。用以下格式分隔:
178
185
 
179
186
  ---ROLE---
180
187
  tier: major
181
188
  name: <角色名>
182
189
  ---CONTENT---
183
- (这里写段落式角色卡,下面的小标题必须全部出现,每段至少 3 行正经段落,不要写表格)
190
+ (这里写散文角色卡,下面的小标题必须全部出现,每段至少 3 行正经散文,不要写表格)
184
191
 
185
192
  ## 核心标签
186
193
  (3-5 个关键词 + 一句话为什么是这些词)
@@ -189,10 +196,10 @@ name: <角色名>
189
196
  (1-2 个与核心标签反差的具体细节——"冷酷杀手但会给流浪猫留鱼骨"。反差细节是人物立体化的公式,必须有。)
190
197
 
191
198
  ## 人物小传(过往经历)
192
- (一段段落,说这个人怎么变成现在这样。童年/重大事件/塑造性格的那件事。只写关键过往,简版。)
199
+ (一段散文,说这个人怎么变成现在这样。童年/重大事件/塑造性格的那件事。只写关键过往,简版。)
193
200
 
194
- ## 主角线(起点 → 终点 → 代价)
195
- **只有主角必须写本段;其他 major 角色如果分量重也可以写,否则略过。**主角从哪里出发(身份、处境、核心缺陷、一开始最想要什么),到哪里落脚(最终变成什么样的人、拿到/失去什么),为了这个落脚他付出了什么不可逆的代价(关系、身体、信念、某段过去)。不要只写"变强"这种平面变化,要写**内在的位移**。写足写实。
201
+ ## 主角弧线(起点 → 终点 → 代价)
202
+ **只有主角必须写本段;其他 major 角色如果弧线分量重也可以写,否则略过。**主角从哪里出发(身份、处境、核心缺陷、一开始最想要什么),到哪里落脚(最终变成什么样的人、拿到/失去什么),为了这个落脚他付出了什么不可逆的代价(关系、身体、信念、某段过去)。不要只写"变强"这种平面变化,要写**内在的位移**。本段是之前 story_frame.段 2 迁移过来的权威位置,写足写实。
196
203
 
197
204
  ## 当前现状(第 0 章初始状态)
198
205
  (第 0 章时他在哪、做什么、处境如何、最近最烦心的事。**只写角色个人处境**——初始钩子写在 pending_hooks 的 startChapter=0 行;环境/时代锚(如果是需要年份的题材)织进 story_frame.世界观底色。不再有独立的 current_state section。)
@@ -203,7 +210,7 @@ name: <角色名>
203
210
  ## 内在驱动
204
211
  (他想要什么、为什么想要、愿意付出什么代价。)
205
212
 
206
- ## 成长变化
213
+ ## 成长弧光
207
214
  (他在这本书里会经历什么内在位移——变好变坏变复杂,落在哪里。非主角可短可长。)
208
215
 
209
216
  ---ROLE---
@@ -224,7 +231,7 @@ name: <次要角色名>
224
231
 
225
232
  === SECTION: book_rules ===
226
233
 
227
- **只输出 YAML frontmatter 一块——零段落正文。** 所有的"叙事视角 / 本书专属规则 / 核心冲突驱动"等段落已经合并到 story_frame.世界观底色,不要在这里重复写。
234
+ **只输出 YAML frontmatter 一块——零散文。** 所有的"叙事视角 / 本书专属规则 / 核心冲突驱动"等散文已经合并到 story_frame.世界观底色,不要在这里重复写。
228
235
  \`\`\`
229
236
  ---
230
237
  version: "1.0"
@@ -249,29 +256,34 @@ enableFullCastTracking: false
249
256
 
250
257
  === SECTION: pending_hooks ===
251
258
 
252
- 初始伏笔池(Markdown表格,8 列基础版):
253
- | hook_id | 起始章节 | 类型 | 状态 | 最近推进 | 预期回收 | 回收节奏 | 备注 |
259
+ 初始伏笔池(Markdown表格),Phase 7 扩展列:
260
+ | hook_id | 起始章节 | 类型 | 状态 | 最近推进 | 预期回收 | 回收节奏 | 上游依赖 | 回收卷 | 核心 | 半衰期 | 备注 |
254
261
 
255
262
  伏笔表规则:
256
263
  - 第5列必须是纯数字章节号,不能写自然语言描述
257
264
  - 建书阶段所有伏笔都还没正式推进,所以第5列统一填 0
258
265
  - 第7列必须填写:立即 / 近期 / 中程 / 慢烧 / 终局 之一
266
+ - 第8列「上游依赖」:列出必须在本伏笔之前种下/回收的上游 hook_id,格式如 [H003, H007];若无依赖填「无」
267
+ - 第9列「回收卷」:用自然语言写该伏笔计划在哪一卷哪一段回收(例:"第2卷中段"、"终卷终章前")。不强制解析为章号
268
+ - 第10列「核心」:是否主线承重伏笔 true / false。主线承重伏笔一本书最多 3-7 条(主谜团、身世、核心承诺),其余次要伏笔填 false
269
+ - 第11列「半衰期」:可选,整数章数。若不填自动按回收节奏推导(立即/近期 = 10、中程 = 30、慢烧/终局 = 80)
259
270
  - 初始线索放备注列,不放第5列
260
- - **初始世界状态 / 初始敌我关系** 如果有关键信息(例如"主角身上带着父亲的笔记本"、"体制已经开始监视码头"),可以作为 startChapter=0 的种子行录入,备注列说明其"初始状态"属性
271
+ - **初始世界状态 / 初始敌我关系** 如果有关键信息(例如"主角身上带着父亲的笔记本"、"体制已经开始监视码头"),可以作为 startChapter=0 的种子行录入,备注列说明其"初始状态"属性。
261
272
 
262
273
  ## 最后强调
263
274
  - 符合${book.platform}平台口味、${gp.name}题材特征
264
275
  - 主角人设鲜明、行为边界清晰
265
276
  - 伏笔前后呼应、配角有独立动机不是工具人
266
- - **story_frame / volume_map / roles 必须是段落密度,不要退化成 bullet**
267
- - **book_rules 只留 YAML,不要写段落正文**
268
- - **不要输出 rhythm_principles 或 current_state 独立 section**——节奏原则合并进 volume_map 尾段;角色初始状态写在 roles.当前现状,初始钩子写在 pending_hooks(startChapter=0 行),环境/时代锚(仅历史/年代/都市重生等需要年份的题材)织进 story_frame.世界观底色,不要硬凑`;
277
+ - **story_frame / volume_map / roles 必须是散文密度,不要退化成 bullet**
278
+ - **book_rules 只留 YAML,不要写散文**
279
+ - **不要输出 rhythm_principles 或 current_state 独立 section**——节奏原则合并进 volume_map 尾段;角色初始状态写在 roles.当前现状,初始钩子写在 pending_hooks(startChapter=0 行),环境/时代锚(仅历史/年代/都市重生等需要年份的题材)织进 story_frame.世界观底色,不要硬凑
280
+ - **pending_hooks 表必须包含 Phase 7 扩展列——depends_on 标出因果链、pays_off_in_arc 锁定回收大致位置、core_hook 标记主线承重伏笔(3-7 条)、half_life 仅给重点伏笔设置**
281
+
282
+ ## 硬性完结检查(生成前读一遍)
283
+ 必须依次输出全部 **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 最后一行才算交付。`;
269
284
  }
270
- // -------------------------------------------------------------------------
271
- // Phase 5 段落式 prompt — en
272
- // -------------------------------------------------------------------------
273
285
  buildEnglishFoundationPrompt(book, gp, genreBody, contextBlock, reviewFeedbackBlock, numericalBlock, powerBlock, eraBlock) {
274
- 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: the 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}
286
+ 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}
275
287
 
276
288
  ## Book metadata
277
289
  - Platform: ${book.platform}
@@ -291,7 +303,7 @@ ${eraBlock}
291
303
  ## Output contract (5 === SECTION: === blocks)
292
304
 
293
305
  ## Deduplication rule (MANDATORY)
294
- Do not duplicate the same fact across sections. The protagonist's full character arc lives only in roles; world hard-rules live only in story_frame; rhythm principles live only in the last paragraph of volume_map; character initial status lives only in roles.Current_State; initial hooks live only in pending_hooks (start_chapter=0 rows). **When the book is period fiction / historical fanfic / urban reincarnation** — anything pinned to a real year, season, or historic marker — weave the environment/era anchor into story_frame's world-tonal-ground paragraph. **For cultivation / high-fantasy / system genres that have no real-world year, skip it entirely** — do not fabricate an era anchor. If a section repeats content that belongs elsewhere, delete it.
306
+ Do not duplicate the same fact across sections. The protagonist's arc lives only in roles; world hard-rules live only in story_frame; rhythm principles live only in the last paragraph of volume_map; character initial status lives only in roles.Current_State; initial hooks live only in pending_hooks (start_chapter=0 rows). **When the book is period fiction / historical fanfic / urban reincarnation** — anything pinned to a real year, season, or historic marker — weave the environment/era anchor into story_frame's world-tonal-ground paragraph (e.g. "July 1985, just after the SARS wave"). **For cultivation / high-fantasy / system genres that have no real-world year, skip it entirely** — do not fabricate an era anchor. If a section repeats content that belongs elsewhere, delete it.
295
307
 
296
308
  ## Output budget (over-budget means cut)
297
309
  - story_frame ≤ 3000 chars
@@ -302,75 +314,88 @@ Do not duplicate the same fact across sections. The protagonist's full character
302
314
 
303
315
  === SECTION: story_frame ===
304
316
 
305
- Four prose sections, ~600-900 chars each. No tables. No bullet lists. Real paragraphs. **Do NOT write the protagonist's full character arc here** — that is owned by roles/主要角色/<protagonist>.md. Use a single-line pointer inside this block.
317
+ Four prose sections, ~600-900 chars each. No tables. No bullet lists. Real paragraphs. **Do NOT write the protagonist's full arc here** — that is owned by roles/主要角色/<protagonist>.md. Use a single-line pointer inside this block (e.g. "The protagonist is X; full arc lives in roles/主要角色/X.md").
306
318
 
307
319
  ## 01_Theme_and_Tonal_Ground
308
320
  What is this book actually about — not "hero grows from weak to strong" (empty), but a concrete proposition. Then the tonal ground: warm / cold / fierce / severe — which, and why this and not another. End with a one-line pointer to the protagonist role file.
309
321
 
310
- ## 02_Core_Conflict_and_Opponent
322
+ ## 02_Core_Conflict_and_Foreground_Background_Story_Layers
311
323
  The book's main tension — not "good vs evil" but "because A believes X and B believes Y, they will inevitably collide on Z". At least two opponents: one visible, one structural/systemic. Opponents have their own logic.
312
324
 
325
+ **This section must explicitly write out the foreground story / background story layers**:
326
+ - **Foreground story**: the surface conflict the reader sees every chapter (cases, combat, leveling up, romance, business moves). Each volume / arc has its own visible goal and closure point.
327
+ - **Background story**: the hidden machine running through the whole book — the puppet master, conspiracy, origin secret, systemic oppression, fated curse. The reader assembles it from fragments; full payoff lands near the finale.
328
+
329
+ The two layers must be causally linked, not parallel universes — every foreground conflict should trace back to some gear of the background machine turning. **Foreground-only story collapses into a set of disconnected episodes with no forward pull; background-only story is suffocating and never delivers. Write both in prose here, and name how they interlock.**
330
+
313
331
  ## 03_World_Tonal_Ground (hard rules + sensory tone + book-specific rules)
314
- The world's operating rules. 3-5 unbreakable laws written as prose, not bullets. Sensory texture: wet or dry, fast or slow, noisy or quiet — give the writer an anchor. **This paragraph also absorbs the narrative prose that used to live in book_rules** (narrative perspective, core conflict driver, book-specific rules). Write them all here once. Do not repeat them in book_rules.
332
+ The world's operating rules. 3-5 unbreakable laws written as prose, not bullets. Sensory texture: wet or dry, fast or slow, noisy or quiet — give the writer an anchor. **This paragraph also absorbs the narrative prose that used to live in book_rules (narrative perspective, core conflict driver, book-specific rules).** Write them all here once. Do not repeat them in book_rules.
315
333
 
316
- ## 04_Endgame_Direction
334
+ ## 04_Endgame_Direction_and_Book_Objective
317
335
  What the last chapter roughly feels like. The final shot: where, doing what, around whom, thinking what. A distant target for every planner call downstream.
318
336
 
337
+ **End this paragraph with a one-sentence Book Objective** (the root of the recursive OKR outline): when this book is done, the protagonist must reach a **verifiable end-state** (e.g., "rise from errand disciple to sect elder and publicly vindicate the parental case", "go from undocumented migrant worker to running three fur-trade companies and personally putting the ex-husband in prison"). Do NOT use vague words like "grow stronger" or "take revenge" — write a concrete state an outside observer can check "achieved / not achieved". This Book Objective is the root of the full-book OKR outline; volume_map will decompose it per volume below.
338
+
319
339
  === SECTION: volume_map ===
320
340
 
321
- Prose volume map, **5 sections + 1 closing rhythm paragraph**. **Critical requirement: stay at volume-level prose only**. No chapter-level task assignment.
341
+ Prose volume map, **5 sections + 1 closing rhythm paragraph**. **Critical requirement: stay at volume-level prose only** specify each volume's theme, emotional curve, cross-volume hooks, character stage goals, and volume-end irreversible changes. **Do NOT prescribe chapter-level tasks** (no "chapter 17 sends him home"). Chapter planning is the Phase 3 planner's job; the architect builds the skeleton, not the chapter list.
322
342
 
323
343
  ## 01_Volume_Themes_and_Emotional_Curves
324
- How many volumes? Each volume's theme in one sentence; each volume's emotional curve as a paragraph.
344
+ How many volumes? Each volume's theme in one sentence; each volume's emotional curve as a paragraph (where pressured, where rewarding, where cold, where warm). Not mechanical rotation.
325
345
 
326
- ## 02_Cross_Volume_Hooks_and_Payoff_Promises
327
- Volume 1 plants hook A, paid off in volume N; volume 2 plants hook B, paid off in volume M. Prose, not tables. **Stay at volume-level**.
346
+ ## 02_Cross_Volume_Hooks_and_Payoff_Promises (cover BOTH foreground and background layers)
347
+ Volume 1 plants hook A, paid off in volume N; volume 2 plants hook B, paid off in volume M. Prose, not tables. **Stay at volume-level** (e.g., "the origin mystery planted in volume 1 pays off in volume 3"); do not specify chapter numbers.
328
348
 
329
- ## 03_Character_Stage_Goals
330
- Protagonist's state at end of vol 1, vol 2, ... Supporting characters' stage changes. Stage goals only — full character arc lives in roles.
349
+ **Hooks must cover BOTH foreground and background layers** (matching the two-layer story established in story_frame.02):
350
+ - Foreground hooks: short-range arc-level hooks (case mystery, opponent identity, resource grab), paid off within 1-2 volumes
351
+ - Background hooks: full-book main-line hooks (ultimate truth, origin, systemic secret), paid off near the finale. The 3-7 load-bearing ones are core_hook=true
352
+
353
+ **If this paragraph only carries foreground hooks with no background seeds, you have lost the book's forward pull axis. Add them.**
354
+
355
+ ## 03_Per_Volume_OKRs (Objective + 3 Key Results)
356
+ Recursive OKR outline that decomposes the Book Objective (root O set at the end of story_frame.04): every volume must explicitly state:
357
+ - **Objective (volume-level goal)**: a **verifiable state** the protagonist must reach by volume end, one sentence, logically chained to the Book Objective (e.g., if Book O = "become sect elder and vindicate the parental case", then Vol 1 O = "move from errand disciple into the registered disciple roster and recover the first lead pointing to the truth")
358
+ - **Key Results (3 items, quantifiable / observable)**: three concrete sub-achievements whose completion can be checked by an outside observer (e.g., KR1 = "take over the pharmacy garden steward seat", KR2 = "lock in a stable alliance with Lingan Peak", KR3 = "uncover the first half-page fragment of the parental case file"). No vague KRs like "gets stronger" / "matures".
359
+
360
+ Supporting characters' stage changes (master dies end of vol 2, opponent breaks bad in vol 3) go as notes under the relevant KR. Stage only — full arc lives in roles. **The 3 KRs per volume are the direct input for the planner: once it sees 3 KRs for a volume, it paces chapter tasks at roughly one KR advanced every 3-5 chapters.**
331
361
 
332
362
  ## 04_Volume_End_Mandatory_Changes
333
- Each volume's last chapter must contain an irreversible event. Prose, one paragraph per volume.
363
+ Each volume's last chapter must contain an irreversible event. Prose, one paragraph per volume. **Write what must happen, not which chapter**.
334
364
 
335
365
  ## 05_Rhythm_Principles (concrete + universal)
336
- **Single home for rhythm principles — no separate rhythm_principles section.** 6 rhythm principles. **At least 3 must be concretized for this book**; the rest may stay as universal rules.
337
-
338
- ### Golden First Three Chapters Rule
339
- - Chapter 1: throw the core conflict immediately; no large background dump
340
- - Chapter 2: show the core edge / ability / leverage that answers Chapter 1's pressure
341
- - Chapter 3: establish the first concrete short-term goal that gives readers a reason to continue
366
+ **This is the single home for rhythm principles — no separate rhythm_principles section exists.** Output 6 rhythm principles. **At least 3 must be concretized for this book** (e.g., "every 5 chapters in the first 30, hit one small payoff"); the rest may stay as universal rules (e.g., "no deus ex machina", "plant the foreshadow 3-5 chapters before the climax"). A mix of concrete + universal is valid. Bad: "rhythm must balance tension and release". Good: "every 5 chapters in the first 30 carries a small payoff landing in the last 300 chars of the chapter". Cover (order flexible, substitutions of equal weight are allowed): (1) climax spacing, (2) breath frequency, (3) hook density, (4) information release pacing, (5) payoff rhythm, (6) relationship advancement — each 2-3 sentences.
342
367
 
343
368
  === SECTION: roles ===
344
369
 
345
- One-file-per-character prose. **The protagonist card is the single source of truth for the protagonist's full character arc** — story_frame no longer carries it, and writer/planner both read it here.
370
+ One-file-per-character prose. **The protagonist card is the single source of truth for the protagonist's arc** — story_frame no longer carries it, and writer/planner both read it here.
346
371
 
347
372
  ---ROLE---
348
373
  tier: major
349
374
  name: <character name>
350
375
  ---CONTENT---
351
376
  ## Core_Tags
352
- (3-5 tags + one sentence on why)
377
+ (3-5 tags + one sentence on why those tags)
353
378
 
354
379
  ## Contrast_Detail
355
- (1-2 concrete details that contradict the core tags.)
380
+ (1-2 concrete details that contradict the core tags — "ice-cold killer but leaves fish bones for stray cats". Contrast detail is the formula for character dimensionality.)
356
381
 
357
382
  ## Back_Story
358
- (Prose paragraph — how this person became who they are.)
383
+ (Prose paragraph — how this person became who they are. Key past only, keep it lean.)
359
384
 
360
385
  ## Protagonist_Arc (start → end → cost)
361
- **Mandatory for the protagonist; optional for other majors with substantial arcs.** Internal displacement, not just growth.
386
+ **Mandatory for the protagonist; optional for other majors with substantial arcs.** Where they start (identity, situation, core flaw, initial desire); where they land (who they become, what they gain or lose); the irreversible cost they pay for that landing. Show internal displacement, not just growth. This section absorbs what used to live in story_frame.02_Protagonist_Arc.
362
387
 
363
388
  ## Current_State (initial state at chapter 0)
364
- (Character-only: initial hooks go in pending_hooks start_chapter=0 rows; environment/era anchors weave into story_frame.)
389
+ (Where they are at chapter 0, what's on their mind, most recent worry. **Character-only**: initial hooks go in pending_hooks start_chapter=0 rows; environment / era anchors (when the genre has a real year) are woven into story_frame's world-tonal-ground paragraph. No separate current_state section is produced.)
365
390
 
366
391
  ## Relationship_Network
367
- (With protagonist, with other majors. One line each.)
392
+ (With protagonist, with other major characters. One line each. Relationships are dynamic, not labels.)
368
393
 
369
394
  ## Inner_Driver
370
395
  (What they want, why, what they're willing to pay.)
371
396
 
372
397
  ## Growth_Arc
373
- (Internal displacement across the book.)
398
+ (Internal displacement across the book. Can be short for non-protagonists.)
374
399
 
375
400
  ---ROLE---
376
401
  tier: major
@@ -378,19 +403,19 @@ name: <next major>
378
403
  ---CONTENT---
379
404
  ...
380
405
 
381
- (Aim for 2-3 majors + 2-3 supporting majors. Quality over quantity.)
406
+ (Aim for 2-3 majors + 2-3 supporting majors. Quality over quantity — do not pad.)
382
407
 
383
408
  ---ROLE---
384
409
  tier: minor
385
410
  name: <minor name>
386
411
  ---CONTENT---
387
- (Simplified: Core_Tags / Contrast_Detail / Current_State / Relationship_to_Protagonist only, 1-2 lines each.)
412
+ (Simplified: only 4 sections — Core_Tags / Contrast_Detail / Current_State / Relationship_to_Protagonist, 1-2 lines each.)
388
413
 
389
414
  (3-5 minors.)
390
415
 
391
416
  === SECTION: book_rules ===
392
417
 
393
- **Output ONLY the YAML frontmatter block — zero prose.** All narrative guidance moved into story_frame.03_World_Tonal_Ground.
418
+ **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.
394
419
  \`\`\`
395
420
  ---
396
421
  version: "1.0"
@@ -415,15 +440,19 @@ enableFullCastTracking: false
415
440
 
416
441
  === SECTION: pending_hooks ===
417
442
 
418
- Initial hook pool (Markdown table, 8-column base):
419
- | hook_id | start_chapter | type | status | last_advanced_chapter | expected_payoff | payoff_timing | notes |
443
+ Initial hook pool (Markdown table), Phase 7 extended columns:
444
+ | hook_id | start_chapter | type | status | last_advanced_chapter | expected_payoff | payoff_timing | depends_on | pays_off_in_arc | core_hook | half_life | notes |
420
445
 
421
446
  Rules:
422
447
  - Column 5 is a pure chapter number, not narrative description
423
448
  - At book creation all planned hooks have last_advanced_chapter = 0
424
449
  - Column 7 must be: immediate / near-term / mid-arc / slow-burn / endgame
450
+ - 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
451
+ - 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
452
+ - Column 10 (core_hook): true / false. Core hooks are main-line load-bearing (central mystery, identity, key promise). A book typically has 3-7 cores; everything else is false
453
+ - Column 11 (half_life): optional integer chapters. If blank, derived from payoff_timing (immediate/near-term = 10, mid-arc = 30, slow-burn/endgame = 80)
425
454
  - Put initial signal text in notes, not column 5
426
- - **Initial world / alliance state**: any load-bearing initial condition can be seeded as a start_chapter=0 row with a notes tag indicating its initial-state nature
455
+ - **Initial world / alliance state**: any load-bearing initial condition ("protagonist carries the father's notebook", "the regime already watches the harbor") can be seeded as a start_chapter=0 row with a note-column tag indicating its initial-state nature.
427
456
 
428
457
  ## Final emphasis
429
458
  - Fit ${book.platform} platform taste and ${gp.name} genre traits
@@ -431,22 +460,156 @@ Rules:
431
460
  - Hooks planted with payoff promises; supporting characters have independent motivation
432
461
  - **story_frame / volume_map / roles must be prose density — no bullet-list degradation**
433
462
  - **book_rules is YAML only — no prose body**
434
- - **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`;
463
+ - **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
464
+ - **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**
465
+
466
+ ## Hard completeness check (read before generating)
467
+ 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.`;
468
+ }
469
+ // -------------------------------------------------------------------------
470
+ // Parsing
471
+ // -------------------------------------------------------------------------
472
+ parseSections(content, language) {
473
+ const parsedSections = new Map();
474
+ const sectionPattern = /^\s*===\s*SECTION\s*[::]\s*([^\n=]+?)\s*===\s*$/gim;
475
+ const matches = [...content.matchAll(sectionPattern)];
476
+ for (let i = 0; i < matches.length; i++) {
477
+ const match = matches[i];
478
+ const rawName = match[1] ?? "";
479
+ const start = (match.index ?? 0) + match[0].length;
480
+ const end = matches[i + 1]?.index ?? content.length;
481
+ const normalizedName = this.normalizeSectionName(rawName);
482
+ parsedSections.set(normalizedName, content.slice(start, end).trim());
483
+ }
484
+ // Phase 5 new sections take precedence.
485
+ const storyFrame = parsedSections.get("story_frame") ?? "";
486
+ const volumeMap = parsedSections.get("volume_map") ?? "";
487
+ const rhythmPrinciples = parsedSections.get("rhythm_principles") ?? "";
488
+ const rolesRaw = parsedSections.get("roles") ?? "";
489
+ // Legacy sections (still produced for back-compat where needed).
490
+ // If the model used old section names we still accept them.
491
+ const legacyStoryBible = parsedSections.get("story_bible") ?? "";
492
+ const legacyVolumeOutline = parsedSections.get("volume_outline") ?? "";
493
+ const bookRules = parsedSections.get("book_rules");
494
+ // Phase 5 consolidation: current_state is no longer a required section.
495
+ // Legacy books (v12 / Phase 5 initial / pre-revert) and import/fanfic
496
+ // regenerations may still produce it — accept the value when present,
497
+ // fall through to empty seed when absent (consolidator will populate at
498
+ // runtime). Era/setting anchors that used to motivate a separate
499
+ // current_state block now live naturally inside story_frame.世界观底色
500
+ // for genres that have a real-world year anchor; other genres (修仙/玄幻/
501
+ // 系统文) omit them entirely.
502
+ const currentStateLegacy = parsedSections.get("current_state") ?? "";
503
+ const pendingHooksRaw = parsedSections.get("pending_hooks");
504
+ // 5-section required contract: story_frame (or legacy story_bible),
505
+ // volume_map (or legacy volume_outline), roles, book_rules, pending_hooks.
506
+ //
507
+ // Backward compat: v12 outputs used story_bible/volume_outline and
508
+ // embedded character data inside story_bible — they had no roles block.
509
+ // When the model uses ONLY legacy section names, we accept an empty roles
510
+ // list (consolidator/readers fall back to the character_matrix shim).
511
+ // When the new story_frame / volume_map names are used we require roles.
512
+ const usingLegacyOutlineNames = !storyFrame && !volumeMap
513
+ && (legacyStoryBible.length > 0 || legacyVolumeOutline.length > 0);
514
+ const missing = [];
515
+ const effectiveStoryFrame = storyFrame || legacyStoryBible;
516
+ const effectiveVolumeMap = volumeMap || legacyVolumeOutline;
517
+ if (!effectiveStoryFrame)
518
+ missing.push("story_frame");
519
+ if (!effectiveVolumeMap)
520
+ missing.push("volume_map");
521
+ if (!rolesRaw.trim() && !usingLegacyOutlineNames)
522
+ missing.push("roles");
523
+ if (!bookRules)
524
+ missing.push("book_rules");
525
+ if (!pendingHooksRaw)
526
+ missing.push("pending_hooks");
527
+ if (missing.length > 0) {
528
+ throw new Error(`Architect output missing required section${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`);
529
+ }
530
+ const roles = this.parseRoles(rolesRaw);
531
+ const pendingHooks = this.normalizePendingHooksSection(this.stripTrailingAssistantCoda(pendingHooksRaw), effectiveVolumeMap);
532
+ // Synthesize legacy-facing content from new prose (so back-compat callers
533
+ // still receive real content instead of empty strings).
534
+ const storyBible = legacyStoryBible || this.buildStoryBibleShim(effectiveStoryFrame, language);
535
+ const volumeOutline = legacyVolumeOutline || effectiveVolumeMap;
536
+ return {
537
+ storyBible,
538
+ volumeOutline,
539
+ bookRules: bookRules,
540
+ // currentState: empty string when architect no longer emits the section;
541
+ // writeFoundationFiles seeds current_state.md with a placeholder so
542
+ // consolidator / state-bootstrap readers find a valid file on first boot.
543
+ currentState: currentStateLegacy,
544
+ pendingHooks,
545
+ storyFrame: effectiveStoryFrame,
546
+ volumeMap: effectiveVolumeMap,
547
+ rhythmPrinciples,
548
+ roles,
549
+ };
435
550
  }
436
551
  /**
437
- * Write architect foundation output to disk.
438
- *
439
- * @param mode
440
- * - "init"(默认):首次建书。写架构稿 + 初始化所有运行时状态文件
441
- * (current_state / pending_hooks / particle_ledger / subplot_board /
442
- * emotional_arcs)为空模板。
443
- * - "revise":在已有书上重写架构稿。**只改架构稿相关文件**——outline/ /
444
- * roles/ / 4 个 legacy shim——**完全不动运行时状态文件**。这和
445
- * context-transform 注入给 LLM 的 upgrade hint 承诺"只改架构稿不动已写
446
- * 章节"一致;如果在 revise 模式下触动运行时文件,会把 consolidator 累积
447
- * 的章节状态、伏笔推进、资源账本等全部重置。
552
+ * Parse ---ROLE---...---CONTENT---... blocks from the roles section.
553
+ * Drops malformed entries silently — this is prose the LLM produced,
554
+ * not machine input.
448
555
  */
449
- async writeFoundationFiles(bookDir, output, numericalSystem = true, language = "zh", mode = "init") {
556
+ parseRoles(raw) {
557
+ if (!raw.trim())
558
+ return [];
559
+ const blocks = raw.split(/^---ROLE---$/m).map((chunk) => chunk.trim()).filter(Boolean);
560
+ const roles = [];
561
+ for (const block of blocks) {
562
+ const contentSplit = block.split(/^---CONTENT---$/m);
563
+ if (contentSplit.length < 2)
564
+ continue;
565
+ const headerRaw = contentSplit[0].trim();
566
+ const content = contentSplit.slice(1).join("\n---CONTENT---\n").trim();
567
+ const tierMatch = headerRaw.match(/tier\s*[::]\s*(major|minor|主要|次要)/i);
568
+ const nameMatch = headerRaw.match(/name\s*[::]\s*(.+)/i);
569
+ if (!tierMatch || !nameMatch)
570
+ continue;
571
+ const tierValue = tierMatch[1].toLowerCase();
572
+ const tier = (tierValue === "major" || tierValue === "主要") ? "major" : "minor";
573
+ const name = nameMatch[1].trim();
574
+ if (!name || !content)
575
+ continue;
576
+ roles.push({ tier, name, content });
577
+ }
578
+ return roles;
579
+ }
580
+ buildStoryBibleShim(storyFrame, language) {
581
+ if (language === "en") {
582
+ return `# Story Bible (compat pointer — deprecated)\n\n> This file is kept for external readers only. The authoritative source is now:\n> - outline/story_frame.md (theme / tonal ground / core conflict / world rules / endgame)\n> - outline/volume_map.md (chapter-granular plot map)\n> - roles/ directory (one-file-per-character sheets)\n\n## Excerpt from story_frame\n\n${storyFrame.slice(0, 2000)}\n`;
583
+ }
584
+ return `# 故事圣经(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威来源已迁移至:\n> - outline/story_frame.md(主题 / 基调 / 核心冲突 / 世界铁律 / 终局)\n> - outline/volume_map.md(章级别的分卷地图)\n> - roles/ 文件夹(一人一卡角色档案)\n\n## story_frame 摘录\n\n${storyFrame.slice(0, 2000)}\n`;
585
+ }
586
+ buildCharacterMatrixShim(roles, language) {
587
+ const majorLines = roles.filter((role) => role.tier === "major")
588
+ .map((role) => `- roles/主要角色/${role.name}.md`);
589
+ const minorLines = roles.filter((role) => role.tier === "minor")
590
+ .map((role) => `- roles/次要角色/${role.name}.md`);
591
+ if (language === "en") {
592
+ return `# Character Matrix (compat pointer — deprecated)\n\n> This file is kept for external readers only. Authoritative source is now the roles/ directory (one-file-per-character).\n\n## Major characters\n\n${majorLines.join("\n") || "(none)"}\n\n## Minor characters\n\n${minorLines.join("\n") || "(none)"}\n`;
593
+ }
594
+ return `# 角色矩阵(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威来源已迁移至 roles/ 文件夹(一人一卡)。\n\n## 主要角色\n\n${majorLines.join("\n") || "(无)"}\n\n## 次要角色\n\n${minorLines.join("\n") || "(无)"}\n`;
595
+ }
596
+ buildBookRulesShim(bookRulesBody, language) {
597
+ const trimmedBody = bookRulesBody.trim();
598
+ if (language === "en") {
599
+ const excerpt = trimmedBody
600
+ ? `\n\n## Narrative guidance excerpt\n\n${trimmedBody}\n`
601
+ : "";
602
+ 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}`;
603
+ }
604
+ const excerpt = trimmedBody
605
+ ? `\n\n## 叙事指引摘录\n\n${trimmedBody}\n`
606
+ : "";
607
+ return `# 本书规则(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威 YAML frontmatter(protagonist / prohibitions / genreLock / ...)已迁移至 outline/story_frame.md 顶部。readBookRules() 优先读那里,只有 Phase 5 cleanup #3 之前的老书才会回退到本文件。${excerpt}`;
608
+ }
609
+ // -------------------------------------------------------------------------
610
+ // File writing
611
+ // -------------------------------------------------------------------------
612
+ async writeFoundationFiles(bookDir, output, _numericalSystem = true, language = "zh", mode = "init") {
450
613
  const storyDir = join(bookDir, "story");
451
614
  const outlineDir = join(storyDir, "outline");
452
615
  const rolesDir = join(storyDir, "roles");
@@ -459,84 +622,95 @@ Rules:
459
622
  mkdir(rolesMinorDir, { recursive: true }),
460
623
  ]);
461
624
  const writes = [];
462
- const storyFrame = output.storyFrame ?? "";
463
- const volumeMap = output.volumeMap ?? "";
625
+ const storyFrameBody = output.storyFrame ?? output.storyBible;
626
+ const volumeMap = output.volumeMap ?? output.volumeOutline;
464
627
  const rhythmPrinciples = output.rhythmPrinciples ?? "";
465
628
  const roles = output.roles ?? [];
466
- const isPhase5Output = storyFrame.trim().length > 0;
467
- // debug: 让排查时能一眼看出 LLM 按哪套格式输出、落到哪套文件布局。
468
- // 如果用户新建书后发现只有 story_bible.md / 没有 outline/,看这行日志能
469
- // 确认是 LLM 没按新 prompt 输出(走了 legacy 分支),而不是 writeFoundationFiles
470
- // 本身的 bug。
471
- this.ctx.logger?.info(`[architect] writeFoundationFiles layout=${isPhase5Output ? "phase5" : "legacy"} ` +
472
- `storyFrame=${storyFrame.length}chars volumeMap=${volumeMap.length}chars roles=${roles.length}`);
473
- // revise 模式 + LLM 回退 legacy 输出 → 必须抛错,不写任何文件。
474
- // 原因:如果走 legacy 分支覆盖 story_bible.md 等 flat 文件,而 outline/
475
- // 和 roles/ 保持不变,会让 Phase 5 书进入"outline 新 + shim 老 + roles
476
- // 残留"的混乱状态;读取端的 fallback 逻辑会读到互相矛盾的内容。
477
- // 抛错让用户立刻感知 LLM 响应异常,而不是默默破坏书的数据完整性。
629
+ const isPhase5Output = Boolean(output.storyFrame?.trim());
478
630
  if (mode === "revise" && !isPhase5Output) {
479
631
  throw new Error("Architect revise mode produced legacy-format output (storyFrame empty). " +
480
- "This likely means the LLM didn't follow the reviseFrom prompt. " +
481
- "The book's architecture files have NOT been modified. " +
482
- "Check prompt / model / temperature and try again.");
632
+ "The book's architecture files have NOT been modified.");
483
633
  }
484
- // revise 模式下先清空旧 role 文件,再写本次 architect 输出——避免改名 / 删除 /
485
- // 合并角色后的旧卡片残留被 readRoleCards 当作有效角色继续注入(见 Bug 3)。
486
- // 备份由上游 runner.reviseFoundation 在调用前完成,这里可以安全清空。
487
- // init 模式下目录本来就是空的,不需要清。
488
- // 放在 if (isPhase5Output) 外面,保证所有 revise 场景都清。
489
634
  if (mode === "revise") {
490
635
  await rm(rolesMajorDir, { recursive: true, force: true });
491
636
  await rm(rolesMinorDir, { recursive: true, force: true });
492
637
  await mkdir(rolesMajorDir, { recursive: true });
493
638
  await mkdir(rolesMinorDir, { recursive: true });
494
639
  }
495
- if (isPhase5Output) {
496
- // book_rules 的 YAML frontmatter 提取后拼到 story_frame.md 顶部,作为权威位置。
497
- const { frontmatter: bookRulesFrontmatter, body: bookRulesBody } = extractYamlFrontmatter(output.bookRules);
498
- const storyFrameWithFrontmatter = bookRulesFrontmatter
499
- ? `${bookRulesFrontmatter}\n\n${storyFrame.trim()}\n`
500
- : storyFrame;
501
- // Phase 5 权威文件
502
- writes.push(writeFile(join(outlineDir, "story_frame.md"), storyFrameWithFrontmatter, "utf-8"));
503
- writes.push(writeFile(join(outlineDir, "volume_map.md"), volumeMap, "utf-8"));
504
- if (rhythmPrinciples.trim()) {
505
- const rhythmFileName = language === "en" ? "rhythm_principles.md" : "节奏原则.md";
506
- writes.push(writeFile(join(outlineDir, rhythmFileName), rhythmPrinciples, "utf-8"));
507
- }
508
- // 一人一卡
509
- for (const role of roles) {
510
- const targetDir = role.tier === "major" ? rolesMajorDir : rolesMinorDir;
511
- const safeName = role.name.replace(/[/\\:*?"<>|]/g, "_").trim();
512
- if (!safeName)
513
- continue;
514
- writes.push(writeFile(join(targetDir, `${safeName}.md`), role.content, "utf-8"));
515
- }
516
- // Legacy shim 文件
517
- writes.push(writeFile(join(storyDir, "story_bible.md"), this.buildStoryBibleShim(storyFrameWithFrontmatter, language), "utf-8"));
518
- writes.push(writeFile(join(storyDir, "volume_outline.md"), volumeMap, "utf-8"));
519
- writes.push(writeFile(join(storyDir, "character_matrix.md"), this.buildCharacterMatrixShim(roles, language), "utf-8"));
520
- writes.push(writeFile(join(storyDir, "book_rules.md"), this.buildBookRulesShim(bookRulesBody, language), "utf-8"));
521
- }
522
- else {
523
- // Legacy 输出路径(仅 init 模式——revise + legacy 已在上面抛错):
524
- // LLM 还按老 prompt 输出 story_bible / volume_outline,走 Phase 4 落盘方式。
640
+ if (!isPhase5Output) {
525
641
  writes.push(writeFile(join(storyDir, "story_bible.md"), output.storyBible, "utf-8"));
526
642
  writes.push(writeFile(join(storyDir, "volume_outline.md"), output.volumeOutline, "utf-8"));
527
643
  writes.push(writeFile(join(storyDir, "book_rules.md"), output.bookRules, "utf-8"));
528
644
  writes.push(writeFile(join(storyDir, "character_matrix.md"), language === "en"
529
645
  ? "# Character Matrix\n\n<!-- One ## section per character. Add new characters as new ## blocks. -->\n"
530
646
  : "# 角色矩阵\n\n<!-- 每个角色一个 ## 块,新角色追加新 ## 即可。 -->\n", "utf-8"));
647
+ if (mode === "init") {
648
+ const currentStateSeed = output.currentState?.trim()
649
+ ? output.currentState
650
+ : (language === "en"
651
+ ? "# Current State\n\n> Seeded at book creation. Runtime state is appended by the consolidator after each chapter.\n"
652
+ : "# 当前状态\n\n> 建书时占位。运行时每章之后由 consolidator 追加最新状态。\n");
653
+ writes.push(writeFile(join(storyDir, "current_state.md"), currentStateSeed, "utf-8"));
654
+ writes.push(writeFile(join(storyDir, "pending_hooks.md"), output.pendingHooks, "utf-8"));
655
+ writes.push(writeFile(join(storyDir, "emotional_arcs.md"), language === "en"
656
+ ? "# Emotional Arcs\n\n| Character | Chapter | Emotional State | Trigger Event | Intensity (1-10) | Arc Direction |\n| --- | --- | --- | --- | --- | --- |\n"
657
+ : "# 情感弧线\n\n| 角色 | 章节 | 情绪状态 | 触发事件 | 强度(1-10) | 弧线方向 |\n|------|------|----------|----------|------------|----------|\n", "utf-8"));
658
+ }
659
+ await Promise.all(writes);
660
+ return;
661
+ }
662
+ // Cleanup #3: book_rules YAML frontmatter is now the authoritative
663
+ // schema for structured fields (protagonist, prohibitions, …). We prepend
664
+ // it to story_frame.md so readers have one canonical place to look.
665
+ // book_rules.md becomes a compat shim.
666
+ const { frontmatter: bookRulesFrontmatter, body: bookRulesBody } = extractYamlFrontmatter(output.bookRules);
667
+ const storyFrame = bookRulesFrontmatter
668
+ ? `${bookRulesFrontmatter}\n\n${storyFrameBody.trim()}\n`
669
+ : storyFrameBody;
670
+ // Phase 5 primary prose files
671
+ writes.push(writeFile(join(outlineDir, "story_frame.md"), storyFrame, "utf-8"));
672
+ writes.push(writeFile(join(outlineDir, "volume_map.md"), volumeMap, "utf-8"));
673
+ // Phase 5 consolidation: rhythm principles live inside the last paragraph
674
+ // of volume_map. A separate 节奏原则.md / rhythm_principles.md file is only
675
+ // written when the architect happened to produce a standalone block (legacy
676
+ // 7-section output / foundation-reviewer round-trips that still split it
677
+ // out). Skipping the empty write avoids 0-byte files that mislead the UI
678
+ // and fight against the "no duplication" rule — readers who need the rhythm
679
+ // content already pull it from volume_map's closing paragraph.
680
+ if (rhythmPrinciples.trim()) {
681
+ const rhythmFileName = language === "en" ? "rhythm_principles.md" : "节奏原则.md";
682
+ writes.push(writeFile(join(outlineDir, rhythmFileName), rhythmPrinciples, "utf-8"));
683
+ }
684
+ // Roles — one file per character
685
+ for (const role of roles) {
686
+ const targetDir = role.tier === "major" ? rolesMajorDir : rolesMinorDir;
687
+ const safeName = role.name.replace(/[/\\:*?"<>|]/g, "_").trim();
688
+ if (!safeName)
689
+ continue;
690
+ writes.push(writeFile(join(targetDir, `${safeName}.md`), role.content, "utf-8"));
531
691
  }
532
- // 运行时状态文件——**只在 init 模式写**。revise 模式下这些文件已经存在且
533
- // consolidator 累积了章节状态(伏笔进度、角色位置、资源账本、情感曲线
534
- // 等),重写会把已写章节的真实状态全部清零,违反 context-transform 里给
535
- // LLM 的承诺"升级只改架构稿,不动已写的章节"(见 Bug 1)。
692
+ // Compat shims these are pointer files, not authoritative content.
693
+ writes.push(writeFile(join(storyDir, "story_bible.md"), this.buildStoryBibleShim(storyFrame, language), "utf-8"));
694
+ writes.push(writeFile(join(storyDir, "character_matrix.md"), this.buildCharacterMatrixShim(roles, language), "utf-8"));
695
+ // Cleanup #1: volume_outline.md mirror removed. All readers now resolve
696
+ // through readVolumeMap() in utils/outline-paths.ts, which prefers
697
+ // outline/volume_map.md and falls back to legacy volume_outline.md for
698
+ // books initialized before Phase 5.
699
+ // book_rules.md is now a compat shim — the authoritative YAML
700
+ // frontmatter lives on story_frame.md (cleanup #3). readBookRules()
701
+ // prefers story_frame.md but still falls back here for older books.
702
+ writes.push(writeFile(join(storyDir, "book_rules.md"), this.buildBookRulesShim(bookRulesBody, language), "utf-8"));
703
+ // Runtime state files.
704
+ // Phase 5 consolidation: the architect no longer emits a current_state
705
+ // section (only 3 genres — 港综同人/年代文/都市重生 — benefit from a
706
+ // separate era anchor, and those fold naturally into story_frame.世界观底色).
707
+ // We still write current_state.md with a seed placeholder so
708
+ // isCompleteBookDirectory() sees it on first boot and the runtime
709
+ // consolidator has a file to append each chapter's state into.
710
+ // Per-character state lives in roles/*.Current_State; initial hook rows
711
+ // live in pending_hooks with start_chapter=0. Legacy books / imports that
712
+ // still produced the section keep their content as-is.
536
713
  if (mode === "init") {
537
- // current_state.md — 架构师不再产出结构化初始状态,给占位 seed;运行时由
538
- // consolidator 每章追加。如果 output 里带了内容(legacy 输出或 reviser
539
- // 生成),直接用。
540
714
  const currentStateSeed = output.currentState?.trim()
541
715
  ? output.currentState
542
716
  : (language === "en"
@@ -544,25 +718,22 @@ Rules:
544
718
  : "# 当前状态\n\n> 建书时占位。运行时每章之后由 consolidator 追加最新状态。每个角色的初始状态详见 roles/*.当前现状;承重的初始世界设定见 pending_hooks 里 startChapter=0 的行。\n");
545
719
  writes.push(writeFile(join(storyDir, "current_state.md"), currentStateSeed, "utf-8"));
546
720
  writes.push(writeFile(join(storyDir, "pending_hooks.md"), output.pendingHooks, "utf-8"));
547
- // 运行时 append log 文件,下游 state-validator / consolidator 依赖这些存在。
548
- if (numericalSystem) {
549
- writes.push(writeFile(join(storyDir, "particle_ledger.md"), language === "en"
550
- ? "# Resource Ledger\n\n| Chapter | Opening Value | Source | Integrity | Delta | Closing Value | Evidence |\n| --- | --- | --- | --- | --- | --- | --- |\n| 0 | 0 | Initialization | - | 0 | 0 | Initial book state |\n"
551
- : "# 资源账本\n\n| 章节 | 期初值 | 来源 | 完整度 | 增量 | 期末值 | 依据 |\n|------|--------|------|--------|------|--------|------|\n| 0 | 0 | 初始化 | - | 0 | 0 | 开书初始 |\n", "utf-8"));
552
- }
553
- writes.push(writeFile(join(storyDir, "subplot_board.md"), language === "en"
554
- ? "# Subplot Board\n\n| Subplot ID | Subplot | Related Characters | Start Chapter | Last Active Chapter | Chapters Since | Status | Progress Summary | Payoff ETA |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n"
555
- : "# 支线进度板\n\n| 支线ID | 支线名 | 相关角色 | 起始章 | 最近活跃章 | 距今章数 | 状态 | 进度概述 | 回收ETA |\n|--------|--------|----------|--------|------------|----------|------|----------|---------|\n", "utf-8"));
556
721
  writes.push(writeFile(join(storyDir, "emotional_arcs.md"), language === "en"
557
722
  ? "# Emotional Arcs\n\n| Character | Chapter | Emotional State | Trigger Event | Intensity (1-10) | Arc Direction |\n| --- | --- | --- | --- | --- | --- |\n"
558
723
  : "# 情感弧线\n\n| 角色 | 章节 | 情绪状态 | 触发事件 | 强度(1-10) | 弧线方向 |\n|------|------|----------|----------|------------|----------|\n", "utf-8"));
559
724
  }
725
+ // Cleanup #2 (Option B): particle_ledger.md / subplot_board.md /
726
+ // chapter_summaries.md are pure runtime logs appended by the writer's
727
+ // settlement phase. The architect no longer seeds them here — mixing a
728
+ // static "setting" seed with a runtime "append log" was the dual-purpose
729
+ // mess that prompted the cleanup. If they don't exist yet, downstream
730
+ // readers see the placeholder and the first chapter settlement creates
731
+ // them naturally. The `_numericalSystem` parameter is kept for API
732
+ // compatibility with existing callers.
560
733
  await Promise.all(writes);
561
734
  }
562
735
  /**
563
736
  * Reverse-engineer foundation from existing chapters.
564
- * Reads all chapters as a single text block and asks LLM to extract story_bible,
565
- * volume_outline, book_rules, current_state, and pending_hooks.
566
737
  */
567
738
  async generateFoundationFromImport(book, chaptersText, externalContext, reviewFeedback, options) {
568
739
  const { profile: gp, body: genreBody } = await readGenreProfile(this.ctx.projectRoot, book.genre);
@@ -575,306 +746,73 @@ Rules:
575
746
  : "";
576
747
  const numericalBlock = gp.numericalSystem
577
748
  ? (resolvedLanguage === "en"
578
- ? `- The story uses a trackable numerical/resource system
579
- - Define numericalSystemOverrides in book_rules (hardCap, resourceTypes)`
580
- : `- 有明确的数值/资源体系可追踪
581
- - 在 book_rules 中定义 numericalSystemOverrides(hardCap、resourceTypes)`)
749
+ ? "- The story uses a trackable numerical/resource system"
750
+ : "- 有明确的数值/资源体系可追踪")
582
751
  : (resolvedLanguage === "en"
583
- ? "- This genre has no explicit numerical system and does not need a resource ledger"
584
- : "- 本题材无数值系统,不需要资源账本");
585
- const powerBlock = gp.powerScaling
586
- ? (resolvedLanguage === "en" ? "- The story has an explicit power-scaling ladder" : "- 有明确的战力等级体系")
587
- : "";
588
- const eraBlock = gp.eraResearch
589
- ? (resolvedLanguage === "en"
590
- ? "- The story needs era/historical grounding (set eraConstraints in book_rules)"
591
- : "- 需要年代考据支撑(在 book_rules 中设置 eraConstraints)")
592
- : "";
593
- const storyBiblePrompt = resolvedLanguage === "en"
594
- ? `Extract from the source text and organize with structured second-level headings:
595
- ## 01_Worldview
596
- Extracted world setting, core rules, and frame
597
-
598
- ## 02_Protagonist
599
- Inferred protagonist setup (identity / advantage / personality core / behavioral boundaries)
600
-
601
- ## 03_Factions_and_Characters
602
- Factions and important supporting characters that appear in the source text
603
-
604
- ## 04_Geography_and_Environment
605
- Locations, environments, and scene traits drawn from the source text
606
-
607
- ## 05_Title_and_Blurb
608
- Keep the original title "${book.title}" and generate a matching blurb from the source text`
609
- : `从正文中提取,用结构化二级标题组织:
610
- ## 01_世界观
611
- 从正文中提取的世界观设定、核心规则体系
612
-
613
- ## 02_主角
614
- 从正文中推断的主角设定(身份/金手指/性格底色/行为边界)
615
-
616
- ## 03_势力与人物
617
- 从正文中出现的势力分布、重要配角(每人:名字、身份、动机、与主角关系、独立目标)
618
-
619
- ## 04_地理与环境
620
- 从正文中出现的地图/场景设定、环境特色
621
-
622
- ## 05_书名与简介
623
- 保留原书名"${book.title}",根据正文内容生成简介`;
624
- const volumeOutlinePrompt = resolvedLanguage === "en"
625
- ? `Infer the volume plan from existing text:
626
- - Existing chapters: review the actual structure already present
627
- - Future projection: predict later directions from active hooks and plot momentum
628
- For each volume include: title, chapter range, core conflict, and key turning points`
629
- : `基于已有正文反推卷纲:
630
- - 已有章节部分:根据实际内容回顾每卷的结构
631
- - 后续预测部分:基于已有伏笔和剧情走向预测未来方向
632
- 每卷包含:卷名、章节范围、核心冲突、关键转折`;
633
- const bookRulesPrompt = resolvedLanguage === "en"
634
- ? `Infer book_rules.md as YAML frontmatter plus narrative guidance from character behavior in the source text:
635
- \`\`\`
636
- ---
637
- version: "1.0"
638
- protagonist:
639
- name: (extract protagonist name from the text)
640
- personalityLock: [(infer 3-5 personality keywords from behavior)]
641
- behavioralConstraints: [(infer 3-5 behavioral constraints from behavior)]
642
- genreLock:
643
- primary: ${book.genre}
644
- forbidden: [(2-3 forbidden style intrusions)]
645
- ${gp.numericalSystem ? `numericalSystemOverrides:
646
- hardCap: (infer from the text)
647
- resourceTypes: [(extract core resource types from the text)]` : ""}
648
- prohibitions:
649
- - (infer 3-5 book-specific prohibitions from the text)
650
- chapterTypesOverride: []
651
- fatigueWordsOverride: []
652
- additionalAuditDimensions: []
653
- enableFullCastTracking: false
654
- ---
655
-
656
- ## Narrative Perspective
657
- (Infer the narrative perspective and style from the text)
658
-
659
- ## Core Conflict Driver
660
- (Infer the book's core conflict and propulsion from the text)
661
- \`\`\``
662
- : `从正文中角色行为反推 book_rules.md 格式的 YAML frontmatter + 叙事指导:
663
- \`\`\`
664
- ---
665
- version: "1.0"
666
- protagonist:
667
- name: (从正文提取主角名)
668
- personalityLock: [(从行为推断3-5个性格关键词)]
669
- behavioralConstraints: [(从行为推断3-5条行为约束)]
670
- genreLock:
671
- primary: ${book.genre}
672
- forbidden: [(2-3种禁止混入的文风)]
673
- ${gp.numericalSystem ? `numericalSystemOverrides:
674
- hardCap: (从正文推断)
675
- resourceTypes: [(从正文提取核心资源类型)]` : ""}
676
- prohibitions:
677
- - (从正文推断3-5条本书禁忌)
678
- chapterTypesOverride: []
679
- fatigueWordsOverride: []
680
- additionalAuditDimensions: []
681
- enableFullCastTracking: false
682
- ---
683
-
684
- ## 叙事视角
685
- (从正文推断本书叙事视角和风格)
686
-
687
- ## 核心冲突驱动
688
- (从正文推断本书的核心矛盾和驱动力)
689
- \`\`\``;
690
- const currentStatePrompt = resolvedLanguage === "en"
691
- ? `Reflect the state at the end of the latest chapter:
692
- | Field | Value |
693
- | --- | --- |
694
- | Current Chapter | (latest chapter number) |
695
- | Current Location | (location at the end of the latest chapter) |
696
- | Protagonist State | (state at the end of the latest chapter) |
697
- | Current Goal | (current goal) |
698
- | Current Constraint | (current constraint) |
699
- | Current Alliances | (current alliances / opposition) |
700
- | Current Conflict | (current conflict) |`
701
- : `反映最后一章结束时的状态卡:
702
- | 字段 | 值 |
703
- |------|-----|
704
- | 当前章节 | (最后一章章节号) |
705
- | 当前位置 | (最后一章结束时的位置) |
706
- | 主角状态 | (最后一章结束时的状态) |
707
- | 当前目标 | (当前目标) |
708
- | 当前限制 | (当前限制) |
709
- | 当前敌我 | (当前敌我关系) |
710
- | 当前冲突 | (当前冲突) |`;
711
- const pendingHooksPrompt = resolvedLanguage === "en"
712
- ? `Identify all active hooks from the source text (Markdown table):
713
- | hook_id | start_chapter | type | status | latest_progress | expected_payoff | payoff_timing | notes |`
714
- : `从正文中识别的所有伏笔(Markdown表格):
715
- | hook_id | 起始章节 | 类型 | 状态 | 最近推进 | 预期回收 | 回收节奏 | 备注 |`;
716
- const keyPrinciplesPrompt = resolvedLanguage === "en"
717
- ? `## Key Principles
718
-
719
- 1. Derive everything from the source text; do not invent unsupported settings
720
- 2. Hook extraction must be complete: unresolved clues, hints, and foreshadowing all count
721
- 3. Character inference must come from dialogue and behavior, not assumption
722
- 4. Accuracy first; detailed is better than missing crucial information
723
- ${numericalBlock}
724
- ${powerBlock}
725
- ${eraBlock}`
726
- : `## 关键原则
727
-
728
- 1. 一切从正文出发,不要臆造正文中没有的设定
729
- 2. 伏笔识别要完整:悬而未决的线索、暗示、预告都算
730
- 3. 角色推断要准确:从对话和行为推断性格,不要想当然
731
- 4. 准确性优先,宁可详细也不要遗漏
732
- ${numericalBlock}
733
- ${powerBlock}
734
- ${eraBlock}`;
752
+ ? "- No explicit numerical system"
753
+ : "- 本题材无数值系统");
735
754
  const isSeries = options?.importMode === "series";
736
- const continuationDirectiveEn = isSeries
737
- ? `## Continuation Direction Requirements (Critical)
738
- The continuation portion (chapters in volume_outline that have not happened yet) must open up **new narrative space**:
739
- 1. **New conflict dimension**: Do not merely stretch the imported conflict longer. Introduce at least one new conflict vector not yet covered by the source text (new character, new faction, new location, or new time horizon)
740
- 2. **Ignite within 5 chapters**: The first continuation volume must establish a fresh suspense engine within 5 chapters. Do not spend 3 chapters recapping known information
741
- 3. **Scene freshness**: At least 50% of key continuation scenes must happen in locations or situations not already used in the imported chapters
742
- 4. **No repeated meeting rooms**: If the imported chapters end on a meeting/discussion beat, the continuation must restart from action instead of opening another meeting`
743
- : `## Continuation Direction
744
- The volume_outline should naturally extend the existing narrative arc. Continue from where the imported chapters left off — advance existing conflicts, pay off planted hooks, and introduce new complications that arise organically from the current situation. Do not recap known information.`;
745
- const continuationDirectiveZh = isSeries
746
- ? `## 续写方向要求(关键)
747
- 续写部分(volume_outline 中尚未发生的章节)必须设计**新的叙事空间**:
748
- 1. **新冲突维度**:续写不能只是把导入章节的冲突继续拉长。必须引入至少一个原文未涉及的新冲突方向(新角色、新势力、新地点、新时间跨度)
749
- 2. **5章内引爆**:续写的第一卷必须在前5章内建立新悬念,不允许用3章回顾已知信息
750
- 3. **场景新鲜度**:续写部分至少50%的关键场景发生在导入章节未出现的地点或情境中
751
- 4. **不重复会议**:如果导入章节以会议/讨论结束,续写必须从行动开始,不能再开一轮会`
752
- : `## 续写方向
753
- 卷纲应自然延续已有故事线。从导入章节的结尾处接续——推进现有冲突、兑现已埋伏笔、引入从当前局势中有机产生的新变数。不要回顾已知信息。`;
754
- const workingModeEn = isSeries
755
- ? `## Working Mode
756
-
757
- This is not a zero-to-one foundation pass. You must extract durable story truth from the imported chapters **and design a continuation path**. You need to:
758
- 1. Extract worldbuilding, factions, characters, and systems from the source text -> generate story_bible
759
- 2. Infer narrative structure and future arc direction -> generate volume_outline (review existing chapters + design a **new continuation direction**)
760
- 3. Infer protagonist lock, prohibitions, and narrative constraints from character behavior -> generate book_rules
761
- 4. Reflect the latest chapter state -> generate current_state
762
- 5. Extract all active hooks already planted in the text -> generate pending_hooks`
763
- : `## Working Mode
764
-
765
- This is not a zero-to-one foundation pass. You must extract durable story truth from the imported chapters **and preserve a clean continuation path**. You need to:
766
- 1. Extract worldbuilding, factions, characters, and systems from the source text -> generate story_bible
767
- 2. Infer narrative structure and near-future arc direction -> generate volume_outline (review existing chapters + continue naturally from where the imported chapters stop)
768
- 3. Infer protagonist lock, prohibitions, and narrative constraints from character behavior -> generate book_rules
769
- 4. Reflect the latest chapter state -> generate current_state
770
- 5. Extract all active hooks already planted in the text -> generate pending_hooks`;
771
- const workingModeZh = isSeries
772
- ? `## 工作模式
773
-
774
- 这不是从零创建,而是从已有正文中提取和推导,**并设计续写方向**。你需要:
775
- 1. 从正文中提取世界观、势力、角色、力量体系 → 生成 story_bible
776
- 2. 从叙事结构推断卷纲 → 生成 volume_outline(已有章节的回顾 + **续写部分的新方向设计**)
777
- 3. 从角色行为推断主角锁定和禁忌 → 生成 book_rules
778
- 4. 从最新章节状态推断 current_state(反映最后一章结束时的状态)
779
- 5. 从正文中识别已埋伏笔 → 生成 pending_hooks`
780
- : `## 工作模式
781
-
782
- 这不是从零创建,而是从已有正文中提取和推导,**并为自然续写保留清晰延续路径**。你需要:
783
- 1. 从正文中提取世界观、势力、角色、力量体系 → 生成 story_bible
784
- 2. 从叙事结构推断卷纲 → 生成 volume_outline(回顾已有章节,并从导入章节结束处自然接续)
785
- 3. 从角色行为推断主角锁定和禁忌 → 生成 book_rules
786
- 4. 从最新章节状态推断 current_state(反映最后一章结束时的状态)
787
- 5. 从正文中识别已埋伏笔 → 生成 pending_hooks`;
755
+ const continuationDirective = resolvedLanguage === "en"
756
+ ? (isSeries
757
+ ? `## Continuation Direction Requirements
758
+ The continuation portion must open up new narrative space new conflict vector, new location, new time horizon. Ignite within 5 chapters; at least 50% fresh scenes.`
759
+ : `## Continuation Direction
760
+ Naturally extend the existing arc. Advance existing conflicts, pay off planted hooks, introduce new complications organically.`)
761
+ : (isSeries
762
+ ? `## 续写方向要求
763
+ 续写必须引入新叙事空间——新冲突、新地点、新时间。5章内引爆,50%以上场景新鲜。`
764
+ : `## 续写方向
765
+ 自然延续已有叙事弧线。推进现有冲突、兑现已埋伏笔、引入有机新变数。`);
788
766
  const systemPrompt = resolvedLanguage === "en"
789
- ? `You are a professional web-fiction architect. Your task is to reverse-engineer a complete foundation from existing chapters.${contextBlock}
790
-
791
- ${workingModeEn}
792
-
793
- All output sections — story_bible, volume_outline, book_rules, current_state, and pending_hooks — MUST be written in English. Keep the === SECTION: === tags unchanged.
794
-
795
- ${continuationDirectiveEn}
796
- ${reviewFeedbackBlock}
797
- ## Book Metadata
767
+ ? `You are a professional novel architect. Reverse-engineer a prose-density foundation from the source chapters and write the continuation path.${contextBlock}${reviewFeedbackBlock}
798
768
 
769
+ ## Book metadata
799
770
  - Title: ${book.title}
800
771
  - Platform: ${book.platform}
801
772
  - Genre: ${gp.name} (${book.genre})
802
- - Target Chapters: ${book.targetChapters}
803
- - Chapter Target Length: ${book.chapterWordCount}
804
-
805
- ## Genre Profile
773
+ - Target chapters: ${book.targetChapters}
774
+ - Chapter length: ${book.chapterWordCount}
806
775
 
776
+ ## Genre body
807
777
  ${genreBody}
808
778
 
809
- ## Output Contract
810
-
811
- Generate the following sections. Separate every section with === SECTION: <name> ===:
812
-
813
- === SECTION: story_bible ===
814
- ${storyBiblePrompt}
815
-
816
- === SECTION: volume_outline ===
817
- ${volumeOutlinePrompt}
818
-
819
- === SECTION: book_rules ===
820
- ${bookRulesPrompt}
779
+ ${numericalBlock}
821
780
 
822
- === SECTION: current_state ===
823
- ${currentStatePrompt}
781
+ ${continuationDirective}
824
782
 
825
- === SECTION: pending_hooks ===
826
- ${pendingHooksPrompt}
783
+ ## Output contract
784
+ Follow the consolidated 5-section === SECTION: === layout: story_frame, volume_map, roles, book_rules, pending_hooks. Do NOT emit rhythm_principles or current_state — rhythm principles live in the last paragraph of volume_map; character initial status lives in roles.Current_State; initial hooks live in pending_hooks start_chapter=0 rows; era / setting anchors (only when the genre pins to a real year) are woven into story_frame's world-tonal-ground paragraph.
827
785
 
828
- ${keyPrinciplesPrompt}`
829
- : `你是一个专业的网络小说架构师。你的任务是从已有的小说正文中反向推导完整的基础设定。${contextBlock}
786
+ All prose must be derived from the source package. Do not invent settings. If the package says it is compressed, treat chapter catalog + excerpts as evidence for the foundation; the full chapters will be replayed later for detailed truth files. For volume_map, treat existing chapters as "review" (one paragraph) and continuation as prose chapter-level planning. Hook extraction must be complete for the evidence provided.
830
787
 
831
- ${workingModeZh}
832
-
833
- ${continuationDirectiveZh}
834
- ${reviewFeedbackBlock}
835
- ## 书籍信息
788
+ All output MUST be written in English.`
789
+ : `你是专业的网络小说架构师。从已有章节中反向推导散文密度的基础设定,同时设计续写路径。${contextBlock}${reviewFeedbackBlock}
836
790
 
791
+ ## 书籍元信息
837
792
  - 标题:${book.title}
838
793
  - 平台:${book.platform}
839
794
  - 题材:${gp.name}(${book.genre})
840
795
  - 目标章数:${book.targetChapters}章
841
- - 每章字数:${book.chapterWordCount}字
842
-
843
- ## 题材特征
844
796
 
797
+ ## 题材底色
845
798
  ${genreBody}
846
799
 
847
- ## 生成要求
848
-
849
- 你需要生成以下内容,每个部分用 === SECTION: <name> === 分隔:
850
-
851
- === SECTION: story_bible ===
852
- ${storyBiblePrompt}
853
-
854
- === SECTION: volume_outline ===
855
- ${volumeOutlinePrompt}
856
-
857
- === SECTION: book_rules ===
858
- ${bookRulesPrompt}
800
+ ${numericalBlock}
859
801
 
860
- === SECTION: current_state ===
861
- ${currentStatePrompt}
802
+ ${continuationDirective}
862
803
 
863
- === SECTION: pending_hooks ===
864
- ${pendingHooksPrompt}
804
+ ## 输出契约
805
+ 合并后的 5 段 === SECTION: === 结构:story_frame / volume_map / roles / book_rules / pending_hooks。**不要输出 rhythm_principles 或 current_state 两个 section**——节奏原则合并进 volume_map 尾段,角色初始状态合并进 roles.当前现状,初始钩子写在 pending_hooks startChapter=0 行;环境/时代锚(只有年代文 / 历史同人 / 都市重生等真实年份题材需要)织进 story_frame.世界观底色,其他题材直接省略。
865
806
 
866
- ${keyPrinciplesPrompt}`;
807
+ 所有 prose 必须从资料包中推导,不得臆造。若资料包声明为压缩包,把章节目录和正文摘录当作基础设定证据;完整章节会在后续回放阶段逐章进入 truth files。volume_map 中,已有章节作为"回顾段"(一段散文),续写部分写到章级 prose。伏笔识别以资料包提供的证据为准,尽量完整。`;
867
808
  const userMessage = resolvedLanguage === "en"
868
809
  ? `Generate the complete foundation for an imported ${gp.name} novel titled "${book.title}". Write everything in English.\n\n${chaptersText}`
869
- : `以下是《${book.title}》的全部已有正文,请从中反向推导完整基础设定:\n\n${chaptersText}`;
810
+ : `以下是《${book.title}》的已有正文资料包,请从中反向推导完整基础设定:\n\n${chaptersText}`;
870
811
  const response = await this.chat([
871
812
  { role: "system", content: systemPrompt },
872
- {
873
- role: "user",
874
- content: userMessage,
875
- },
876
- ], { temperature: 0.5, maxTokens: 16384 });
877
- return this.parseSections(response.content);
813
+ { role: "user", content: userMessage },
814
+ ], { temperature: 0.5 });
815
+ return this.parseSections(response.content, resolvedLanguage);
878
816
  }
879
817
  async generateFanficFoundation(book, fanficCanon, fanficMode, reviewFeedback) {
880
818
  const { profile: gp, body: genreBody } = await readGenreProfile(this.ctx.projectRoot, book.genre);
@@ -885,75 +823,46 @@ ${keyPrinciplesPrompt}`;
885
823
  ooc: "标注角色性格偏离的起点和驱动事件。偏离必须有逻辑驱动。",
886
824
  cp: "以配对角色的关系线为主线规划卷纲。每卷必须有关系推进节点。",
887
825
  };
888
- const systemPrompt = `你是一个专业的同人小说架构师。你的任务是基于原作正典为同人小说生成基础设定。
826
+ const systemPrompt = `你是专业同人架构师。基于原作正典为同人生成散文密度的基础设定。
889
827
 
890
828
  ## 同人模式:${fanficMode}
891
829
  ${MODE_INSTRUCTIONS[fanficMode]}
892
830
 
893
- ## 新时空要求(关键)
894
- 你必须为这本同人设计一个**原创的叙事空间**,而不是复述原作剧情。具体要求:
895
- 1. **明确分岔点**:story_bible 必须标注"本作从原作的哪个节点分岔",或"本作发生在原作未涉及的什么时空"
896
- 2. **独立核心冲突**:volume_outline 的核心冲突必须是原创的,不是原作情节的翻版。原作角色可以出现,但他们面对的是新问题
897
- 3. **5章内引爆**:volume_outline 的第1卷必须在前5章内建立核心悬念,不允许用3章做铺垫才到引爆点
898
- 4. **场景新鲜度**:至少50%的关键场景发生在原作未出现的地点或情境中
899
-
831
+ ## 新时空要求
832
+ 必须为这本同人设计原创叙事空间,不是复述原作剧情:
833
+ 1. 明确分岔点——story_frame 必须标注本作从原作的哪个节点分岔
834
+ 2. 独立核心冲突——volume_map 的核心冲突必须是原创的
835
+ 3. 5章内引爆
836
+ 4. 场景新鲜度 ≥ 50%
900
837
  ${reviewFeedbackBlock}
901
838
 
902
839
  ## 原作正典
903
840
  ${fanficCanon}
904
841
 
905
- ## 题材特征
842
+ ## 题材底色
906
843
  ${genreBody}
907
844
 
908
- ## 关键原则
909
- 1. **不发明主要角色** 主要角色必须来自原作正典的角色档案
910
- 2. 可以添加原创配角,但必须在 story_bible 中标注为"原创角色"
911
- 3. story_bible 保留原作世界观,标注同人的改动/扩展部分,并明确写出**分岔点**和**新时空设定**
912
- 4. volume_outline 不得复述原作剧情节拍。每卷的核心事件必须是原创的,标注"原创"
913
- 5. book_rules 的 fanficMode 必须设为 "${fanficMode}"
914
- 6. 主角设定来自原作角色档案中的第一个角色(或用户在标题中暗示的角色)
915
-
916
- 你需要生成以下内容,每个部分用 === SECTION: <name> === 分隔:
917
-
918
- === SECTION: story_bible ===
919
- 世界观(基于原作正典)+ 角色列表(原作角色标注来源,原创角色标注"原创")
920
-
921
- === SECTION: volume_outline ===
922
- 卷纲规划。每卷标注:卷名、章节范围、核心事件(标注原作/原创)、关系发展节点
845
+ ## 输出契约
846
+ 严格按合并后的 5 === SECTION: === 块输出:story_frame / volume_map / roles / book_rules / pending_hooks。**不要输出 rhythm_principles 或 current_state**:节奏原则合并进 volume_map 尾段;角色初始状态写在 roles.当前现状,初始钩子写在 pending_hooks startChapter=0 行;环境/时代锚(仅当同人的原作/本作锚定真实年份时)织进 story_frame.世界观底色,其他情况省略。
923
847
 
924
- === SECTION: book_rules ===
925
- \`\`\`
926
- ---
927
- version: "1.0"
928
- protagonist:
929
- name: (从原作角色中选择)
930
- personalityLock: [(从正典角色档案提取)]
931
- behavioralConstraints: [(基于原作行为模式)]
932
- genreLock:
933
- primary: ${book.genre}
934
- forbidden: []
935
- fanficMode: "${fanficMode}"
936
- allowedDeviations: []
937
- prohibitions:
938
- - (3-5条同人特有禁忌)
939
- ---
940
- (叙事视角和风格指导)
941
- \`\`\`
942
-
943
- === SECTION: current_state ===
944
- 初始状态卡(基于正典起始点)
945
-
946
- === SECTION: pending_hooks ===
947
- 初始伏笔池(从正典关键事件和关系中提取)`;
848
+ - 主要角色必须来自原作正典
849
+ - 可添加原创配角,标注"原创"
850
+ - book_rules 的 fanficMode 必须设为 "${fanficMode}"
851
+ - book_rules 只输出 YAML frontmatter,散文写进 story_frame.世界观底色
852
+ - 主角弧线只写在 roles/主要角色/<主角>.md,不在 story_frame 重复
853
+ - 所有 outline 必须是散文密度`;
948
854
  const response = await this.chat([
949
855
  { role: "system", content: systemPrompt },
950
856
  {
951
857
  role: "user",
952
858
  content: `请为标题为"${book.title}"的${fanficMode}模式同人小说生成基础设定。目标${book.targetChapters}章,每章${book.chapterWordCount}字。`,
953
859
  },
954
- ], { temperature: 0.7, maxTokens: 16384 });
955
- return this.parseSections(response.content);
860
+ ], { temperature: 0.7 });
861
+ return this.parseSections(response.content, book.language ?? "zh");
956
862
  }
863
+ // -------------------------------------------------------------------------
864
+ // Helpers
865
+ // -------------------------------------------------------------------------
957
866
  buildReviewFeedbackBlock(reviewFeedback, language) {
958
867
  const trimmed = reviewFeedback?.trim();
959
868
  if (!trimmed)
@@ -969,122 +878,6 @@ ${trimmed}\n`;
969
878
 
970
879
  ${trimmed}\n`;
971
880
  }
972
- parseSections(content) {
973
- const parsedSections = new Map();
974
- const sectionPattern = /^\s*===\s*SECTION\s*[::]\s*([^\n=]+?)\s*===\s*$/gim;
975
- const matches = [...content.matchAll(sectionPattern)];
976
- for (let i = 0; i < matches.length; i++) {
977
- const match = matches[i];
978
- const rawName = match[1] ?? "";
979
- const start = (match.index ?? 0) + match[0].length;
980
- const end = matches[i + 1]?.index ?? content.length;
981
- const normalizedName = this.normalizeSectionName(rawName);
982
- parsedSections.set(normalizedName, content.slice(start, end).trim());
983
- }
984
- // Phase 5 新 sections
985
- const storyFrame = parsedSections.get("story_frame") ?? "";
986
- const volumeMap = parsedSections.get("volume_map") ?? "";
987
- const rhythmPrinciples = parsedSections.get("rhythm_principles") ?? "";
988
- const rolesRaw = parsedSections.get("roles") ?? "";
989
- // Legacy sections — 当 LLM 还按老 prompt 输出时兜底。
990
- const legacyStoryBible = parsedSections.get("story_bible") ?? "";
991
- const legacyVolumeOutline = parsedSections.get("volume_outline") ?? "";
992
- const bookRules = parsedSections.get("book_rules");
993
- const currentStateLegacy = parsedSections.get("current_state") ?? "";
994
- const pendingHooksRaw = parsedSections.get("pending_hooks");
995
- // 用老名字输出且没有 story_frame/volume_map 时,roles 可空(走 legacy shim fallback)。
996
- const usingLegacyOutlineNames = !storyFrame && !volumeMap
997
- && (legacyStoryBible.length > 0 || legacyVolumeOutline.length > 0);
998
- const effectiveStoryFrame = storyFrame || legacyStoryBible;
999
- const effectiveVolumeMap = volumeMap || legacyVolumeOutline;
1000
- const missing = [];
1001
- if (!effectiveStoryFrame)
1002
- missing.push("story_frame");
1003
- if (!effectiveVolumeMap)
1004
- missing.push("volume_map");
1005
- if (!rolesRaw.trim() && !usingLegacyOutlineNames)
1006
- missing.push("roles");
1007
- if (!bookRules)
1008
- missing.push("book_rules");
1009
- if (!pendingHooksRaw)
1010
- missing.push("pending_hooks");
1011
- if (missing.length > 0) {
1012
- throw new Error(`Architect output missing required section${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`);
1013
- }
1014
- const roles = this.parseRoles(rolesRaw);
1015
- const pendingHooks = this.normalizePendingHooksSection(this.stripTrailingAssistantCoda(pendingHooksRaw));
1016
- // Shim-facing 老字段:新 prompt 下用 buildStoryBibleShim 生成指针内容;
1017
- // 老 prompt 下直接用老内容。volumeOutline 也同理。
1018
- const storyBible = legacyStoryBible || effectiveStoryFrame;
1019
- const volumeOutline = legacyVolumeOutline || effectiveVolumeMap;
1020
- return {
1021
- storyBible,
1022
- volumeOutline,
1023
- bookRules: bookRules,
1024
- // current_state 在 Phase 5 下不是必需 section——writeFoundationFiles 会 seed
1025
- // 一个占位文件,consolidator 运行时按章追加。
1026
- currentState: currentStateLegacy,
1027
- pendingHooks,
1028
- storyFrame: effectiveStoryFrame,
1029
- volumeMap: effectiveVolumeMap,
1030
- rhythmPrinciples,
1031
- roles,
1032
- };
1033
- }
1034
- /** Parse ---ROLE---...---CONTENT--- 块。畸形块静默丢弃。 */
1035
- parseRoles(raw) {
1036
- if (!raw.trim())
1037
- return [];
1038
- const blocks = raw.split(/^---ROLE---$/m).map((chunk) => chunk.trim()).filter(Boolean);
1039
- const roles = [];
1040
- for (const block of blocks) {
1041
- const contentSplit = block.split(/^---CONTENT---$/m);
1042
- if (contentSplit.length < 2)
1043
- continue;
1044
- const headerRaw = contentSplit[0].trim();
1045
- const content = contentSplit.slice(1).join("\n---CONTENT---\n").trim();
1046
- const tierMatch = headerRaw.match(/tier\s*[::]\s*(major|minor|主要|次要)/i);
1047
- const nameMatch = headerRaw.match(/name\s*[::]\s*(.+)/i);
1048
- if (!tierMatch || !nameMatch)
1049
- continue;
1050
- const tierValue = tierMatch[1].toLowerCase();
1051
- const tier = (tierValue === "major" || tierValue === "主要") ? "major" : "minor";
1052
- const name = nameMatch[1].trim();
1053
- if (!name || !content)
1054
- continue;
1055
- roles.push({ tier, name, content });
1056
- }
1057
- return roles;
1058
- }
1059
- buildStoryBibleShim(storyFrame, language) {
1060
- if (language === "en") {
1061
- return `# Story Bible (compat pointer — deprecated)\n\n> This file is kept for external readers only. The authoritative source is now:\n> - outline/story_frame.md (theme / tonal ground / core conflict / world rules / endgame)\n> - outline/volume_map.md (volume-level plot map)\n> - roles/ directory (one-file-per-character sheets)\n\n## Excerpt from story_frame\n\n${storyFrame.slice(0, 2000)}\n`;
1062
- }
1063
- return `# 故事圣经(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威来源已迁移至:\n> - outline/story_frame.md(主题 / 基调 / 核心冲突 / 世界铁律 / 终局)\n> - outline/volume_map.md(卷级分卷地图)\n> - roles/ 文件夹(一人一卡角色档案)\n\n## story_frame 摘录\n\n${storyFrame.slice(0, 2000)}\n`;
1064
- }
1065
- buildCharacterMatrixShim(roles, language) {
1066
- const majorLines = roles.filter((role) => role.tier === "major")
1067
- .map((role) => `- roles/主要角色/${role.name}.md`);
1068
- const minorLines = roles.filter((role) => role.tier === "minor")
1069
- .map((role) => `- roles/次要角色/${role.name}.md`);
1070
- if (language === "en") {
1071
- return `# Character Matrix (compat pointer — deprecated)\n\n> This file is kept for external readers only. Authoritative source is now the roles/ directory (one-file-per-character).\n\n## Major characters\n\n${majorLines.join("\n") || "(none)"}\n\n## Minor characters\n\n${minorLines.join("\n") || "(none)"}\n`;
1072
- }
1073
- return `# 角色矩阵(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威来源已迁移至 roles/ 文件夹(一人一卡)。\n\n## 主要角色\n\n${majorLines.join("\n") || "(无)"}\n\n## 次要角色\n\n${minorLines.join("\n") || "(无)"}\n`;
1074
- }
1075
- buildBookRulesShim(bookRulesBody, language) {
1076
- const trimmedBody = bookRulesBody.trim();
1077
- if (language === "en") {
1078
- const excerpt = trimmedBody
1079
- ? `\n\n## Narrative guidance excerpt\n\n${trimmedBody}\n`
1080
- : "";
1081
- return `# Book Rules (compat pointer — deprecated)\n\n> This file is kept for external readers only. The authoritative YAML frontmatter now lives at the top of outline/story_frame.md.${excerpt}`;
1082
- }
1083
- const excerpt = trimmedBody
1084
- ? `\n\n## 叙事指引摘录\n\n${trimmedBody}\n`
1085
- : "";
1086
- return `# 本书规则(兼容指针——已废弃)\n\n> 本文件仅为外部读取保留。权威 YAML frontmatter 已迁移至 outline/story_frame.md 顶部。${excerpt}`;
1087
- }
1088
881
  normalizeSectionName(name) {
1089
882
  return name
1090
883
  .normalize("NFKC")
@@ -1106,7 +899,7 @@ ${trimmed}\n`;
1106
899
  }
1107
900
  return lines.slice(0, cutoff).join("\n").trimEnd();
1108
901
  }
1109
- normalizePendingHooksSection(section) {
902
+ normalizePendingHooksSection(section, volumeMapRaw) {
1110
903
  const rows = section
1111
904
  .split("\n")
1112
905
  .map((line) => line.trim())
@@ -1128,19 +921,78 @@ ${trimmed}\n`;
1128
921
  const seedNote = normalizedProgress === 0 && this.hasNarrativeProgress(rawProgress)
1129
922
  ? (language === "zh" ? `初始线索:${rawProgress}` : `initial signal: ${rawProgress}`)
1130
923
  : "";
1131
- const notes = this.mergeHookNotes(row[6] ?? "", seedNote, language);
1132
- return {
924
+ const phase7 = row.length >= 12;
925
+ const phase6 = row.length >= 8;
926
+ const noteCellIndex = phase7 ? 11 : phase6 ? 7 : 6;
927
+ const notes = this.mergeHookNotes(row[noteCellIndex] ?? "", seedNote, language);
928
+ const base = {
1133
929
  hookId: row[0] || `hook-${index + 1}`,
1134
930
  startChapter: this.parseHookChapterNumber(row[1]),
1135
931
  type: row[2] ?? "",
1136
932
  status: row[3] ?? "open",
1137
933
  lastAdvancedChapter: normalizedProgress,
1138
934
  expectedPayoff: row[5] ?? "",
1139
- payoffTiming: row.length >= 8 ? row[6] ?? "" : "",
1140
- notes: row.length >= 8 ? this.mergeHookNotes(row[7] ?? "", seedNote, language) : notes,
935
+ payoffTiming: phase6 ? row[6] ?? "" : "",
936
+ notes,
1141
937
  };
938
+ if (phase7) {
939
+ base.dependsOn = this.parseDependsOnCell(row[7] ?? "");
940
+ base.paysOffInArc = (row[8] ?? "").trim();
941
+ base.coreHook = this.parseBooleanCell(row[9]);
942
+ const halfLife = this.parseOptionalInt(row[10]);
943
+ if (halfLife !== undefined)
944
+ base.halfLifeChapters = halfLife;
945
+ }
946
+ return base;
947
+ });
948
+ // Phase 7 hotfix 2: pre-promote seeds based on the three structural rules
949
+ // that don't need runtime advanced_count (core_hook / depends_on /
950
+ // cross_volume). advanced_count-based promotion is applied later by the
951
+ // consolidator at volume boundaries.
952
+ const volumeBoundaries = this.parseVolumeBoundariesForPromotion(volumeMapRaw);
953
+ const allSeedStartChapters = new Map(normalizedHooks.map((hook) => [hook.hookId, hook.startChapter]));
954
+ const promotionContext = {
955
+ volumeBoundaries,
956
+ currentChapter: 0,
957
+ advancedCounts: new Map(),
958
+ allSeedStartChapters,
959
+ };
960
+ const promotedHooks = normalizedHooks.map((hook) => {
961
+ const decision = shouldPromoteHook(hook, promotionContext);
962
+ return { ...hook, promoted: decision.promote };
1142
963
  });
1143
- return renderHookSnapshot(normalizedHooks, language);
964
+ return renderHookSnapshot(promotedHooks, language);
965
+ }
966
+ /**
967
+ * Parse `第N卷 (A-B章)` / `Volume N (chapters A-B)` headers from the
968
+ * architect's volume_map prose. Best-effort: missing / unparseable blocks
969
+ * return an empty list and cross-volume promotion simply never fires.
970
+ */
971
+ parseVolumeBoundariesForPromotion(raw) {
972
+ if (!raw)
973
+ return [];
974
+ const lines = raw.split("\n");
975
+ const volumeHeader = /^(第[一二三四五六七八九十百千万零〇\d]+卷|Volume\s+\d+)/i;
976
+ const rangePattern = /[((]\s*(?:第|[Cc]hapters?\s+)?(\d+)\s*[-–~~—]\s*(\d+)\s*(?:章)?\s*[))]|(?:第|[Cc]hapters?\s+)(\d+)\s*[-–~~—]\s*(\d+)\s*(?:章)?/i;
977
+ const volumes = [];
978
+ for (const rawLine of lines) {
979
+ const line = rawLine.replace(/^#+\s*/, "").trim();
980
+ if (!volumeHeader.test(line))
981
+ continue;
982
+ const rangeMatch = line.match(rangePattern);
983
+ if (!rangeMatch)
984
+ continue;
985
+ const startCh = parseInt(rangeMatch[1] ?? rangeMatch[3] ?? "0", 10);
986
+ const endCh = parseInt(rangeMatch[2] ?? rangeMatch[4] ?? "0", 10);
987
+ if (startCh <= 0 || endCh <= 0)
988
+ continue;
989
+ const rangeIndex = rangeMatch.index ?? line.length;
990
+ const name = line.slice(0, rangeIndex).replace(/[((]\s*$/, "").trim();
991
+ if (name.length > 0) {
992
+ volumes.push({ name, startCh, endCh });
993
+ }
994
+ }
995
+ return volumes;
1144
996
  }
1145
997
  parseHookChapterNumber(value) {
1146
998
  if (!value)
@@ -1148,6 +1000,35 @@ ${trimmed}\n`;
1148
1000
  const match = value.match(/\d+/);
1149
1001
  return match ? parseInt(match[0], 10) : 0;
1150
1002
  }
1003
+ parseDependsOnCell(value) {
1004
+ const trimmed = value.trim();
1005
+ if (!trimmed)
1006
+ return [];
1007
+ const lower = trimmed.toLowerCase();
1008
+ if (lower === "none" || lower === "n/a" || lower === "-" || trimmed === "无")
1009
+ return [];
1010
+ const stripped = trimmed.replace(/^[\[\(]\s*/, "").replace(/\s*[\]\)]$/, "");
1011
+ return stripped
1012
+ .split(/[,,、\/]+/)
1013
+ .map((item) => item.trim().replace(/^\*\*(.+)\*\*$/, "$1").trim())
1014
+ .filter((item) => item.length > 0);
1015
+ }
1016
+ parseBooleanCell(value) {
1017
+ const normalized = (value ?? "").trim().toLowerCase();
1018
+ if (!normalized)
1019
+ return false;
1020
+ return /^(true|yes|y|是|核心|core|1|✓|✔)$/.test(normalized);
1021
+ }
1022
+ parseOptionalInt(value) {
1023
+ const normalized = (value ?? "").trim();
1024
+ if (!normalized)
1025
+ return undefined;
1026
+ const match = normalized.match(/\d+/);
1027
+ if (!match)
1028
+ return undefined;
1029
+ const parsed = parseInt(match[0], 10);
1030
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
1031
+ }
1151
1032
  hasNarrativeProgress(value) {
1152
1033
  const normalized = (value ?? "").trim().toLowerCase();
1153
1034
  if (!normalized)