@kienmatu/inkos-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1104) hide show
  1. package/LICENSE +661 -0
  2. package/dist/agent/agent-session.d.ts +100 -0
  3. package/dist/agent/agent-session.d.ts.map +1 -0
  4. package/dist/agent/agent-session.js +1102 -0
  5. package/dist/agent/agent-session.js.map +1 -0
  6. package/dist/agent/agent-system-prompt.d.ts +12 -0
  7. package/dist/agent/agent-system-prompt.d.ts.map +1 -0
  8. package/dist/agent/agent-system-prompt.js +596 -0
  9. package/dist/agent/agent-system-prompt.js.map +1 -0
  10. package/dist/agent/agent-tools.d.ts +508 -0
  11. package/dist/agent/agent-tools.d.ts.map +1 -0
  12. package/dist/agent/agent-tools.js +3083 -0
  13. package/dist/agent/agent-tools.js.map +1 -0
  14. package/dist/agent/chapter-import-source.d.ts +16 -0
  15. package/dist/agent/chapter-import-source.d.ts.map +1 -0
  16. package/dist/agent/chapter-import-source.js +48 -0
  17. package/dist/agent/chapter-import-source.js.map +1 -0
  18. package/dist/agent/context-transform.d.ts +13 -0
  19. package/dist/agent/context-transform.d.ts.map +1 -0
  20. package/dist/agent/context-transform.js +183 -0
  21. package/dist/agent/context-transform.js.map +1 -0
  22. package/dist/agent/film-authoring-tools.d.ts +98 -0
  23. package/dist/agent/film-authoring-tools.d.ts.map +1 -0
  24. package/dist/agent/film-authoring-tools.js +405 -0
  25. package/dist/agent/film-authoring-tools.js.map +1 -0
  26. package/dist/agent/forecast-tools.d.ts +22 -0
  27. package/dist/agent/forecast-tools.d.ts.map +1 -0
  28. package/dist/agent/forecast-tools.js +153 -0
  29. package/dist/agent/forecast-tools.js.map +1 -0
  30. package/dist/agent/index.d.ts +8 -0
  31. package/dist/agent/index.d.ts.map +1 -0
  32. package/dist/agent/index.js +8 -0
  33. package/dist/agent/index.js.map +1 -0
  34. package/dist/agent/llm-stub.d.ts +19 -0
  35. package/dist/agent/llm-stub.d.ts.map +1 -0
  36. package/dist/agent/llm-stub.js +134 -0
  37. package/dist/agent/llm-stub.js.map +1 -0
  38. package/dist/agent/pi-stream.d.ts +8 -0
  39. package/dist/agent/pi-stream.d.ts.map +1 -0
  40. package/dist/agent/pi-stream.js +31 -0
  41. package/dist/agent/pi-stream.js.map +1 -0
  42. package/dist/agent/skill-tool.d.ts +42 -0
  43. package/dist/agent/skill-tool.d.ts.map +1 -0
  44. package/dist/agent/skill-tool.js +279 -0
  45. package/dist/agent/skill-tool.js.map +1 -0
  46. package/dist/agent/worker-agent.d.ts +24 -0
  47. package/dist/agent/worker-agent.d.ts.map +1 -0
  48. package/dist/agent/worker-agent.js +288 -0
  49. package/dist/agent/worker-agent.js.map +1 -0
  50. package/dist/agents/ai-tells.d.ts +26 -0
  51. package/dist/agents/ai-tells.d.ts.map +1 -0
  52. package/dist/agents/ai-tells.js +140 -0
  53. package/dist/agents/ai-tells.js.map +1 -0
  54. package/dist/agents/architect.d.ts +78 -0
  55. package/dist/agents/architect.d.ts.map +1 -0
  56. package/dist/agents/architect.js +1184 -0
  57. package/dist/agents/architect.js.map +1 -0
  58. package/dist/agents/base.d.ts +42 -0
  59. package/dist/agents/base.d.ts.map +1 -0
  60. package/dist/agents/base.js +123 -0
  61. package/dist/agents/base.js.map +1 -0
  62. package/dist/agents/chapter-analyzer.d.ts +30 -0
  63. package/dist/agents/chapter-analyzer.d.ts.map +1 -0
  64. package/dist/agents/chapter-analyzer.js +509 -0
  65. package/dist/agents/chapter-analyzer.js.map +1 -0
  66. package/dist/agents/composer.d.ts +67 -0
  67. package/dist/agents/composer.d.ts.map +1 -0
  68. package/dist/agents/composer.js +936 -0
  69. package/dist/agents/composer.js.map +1 -0
  70. package/dist/agents/consolidator.d.ts +39 -0
  71. package/dist/agents/consolidator.d.ts.map +1 -0
  72. package/dist/agents/consolidator.js +179 -0
  73. package/dist/agents/consolidator.js.map +1 -0
  74. package/dist/agents/continuity.d.ts +45 -0
  75. package/dist/agents/continuity.d.ts.map +1 -0
  76. package/dist/agents/continuity.js +722 -0
  77. package/dist/agents/continuity.js.map +1 -0
  78. package/dist/agents/detection-insights.d.ts +9 -0
  79. package/dist/agents/detection-insights.d.ts.map +1 -0
  80. package/dist/agents/detection-insights.js +54 -0
  81. package/dist/agents/detection-insights.js.map +1 -0
  82. package/dist/agents/detector.d.ts +17 -0
  83. package/dist/agents/detector.d.ts.map +1 -0
  84. package/dist/agents/detector.js +77 -0
  85. package/dist/agents/detector.js.map +1 -0
  86. package/dist/agents/en-prompt-sections.d.ts +4 -0
  87. package/dist/agents/en-prompt-sections.d.ts.map +1 -0
  88. package/dist/agents/en-prompt-sections.js +9 -0
  89. package/dist/agents/en-prompt-sections.js.map +1 -0
  90. package/dist/agents/fanfic-canon-importer.d.ts +16 -0
  91. package/dist/agents/fanfic-canon-importer.d.ts.map +1 -0
  92. package/dist/agents/fanfic-canon-importer.js +164 -0
  93. package/dist/agents/fanfic-canon-importer.js.map +1 -0
  94. package/dist/agents/fanfic-dimensions.d.ts +14 -0
  95. package/dist/agents/fanfic-dimensions.d.ts.map +1 -0
  96. package/dist/agents/fanfic-dimensions.js +63 -0
  97. package/dist/agents/fanfic-dimensions.js.map +1 -0
  98. package/dist/agents/fanfic-prompt-sections.d.ts +5 -0
  99. package/dist/agents/fanfic-prompt-sections.d.ts.map +1 -0
  100. package/dist/agents/fanfic-prompt-sections.js +85 -0
  101. package/dist/agents/fanfic-prompt-sections.js.map +1 -0
  102. package/dist/agents/foundation-reviewer.d.ts +34 -0
  103. package/dist/agents/foundation-reviewer.d.ts.map +1 -0
  104. package/dist/agents/foundation-reviewer.js +174 -0
  105. package/dist/agents/foundation-reviewer.js.map +1 -0
  106. package/dist/agents/observer-prompts.d.ts +10 -0
  107. package/dist/agents/observer-prompts.d.ts.map +1 -0
  108. package/dist/agents/observer-prompts.js +113 -0
  109. package/dist/agents/observer-prompts.js.map +1 -0
  110. package/dist/agents/planner-context.d.ts +53 -0
  111. package/dist/agents/planner-context.d.ts.map +1 -0
  112. package/dist/agents/planner-context.js +238 -0
  113. package/dist/agents/planner-context.js.map +1 -0
  114. package/dist/agents/planner-prompts.d.ts +45 -0
  115. package/dist/agents/planner-prompts.d.ts.map +1 -0
  116. package/dist/agents/planner-prompts.js +391 -0
  117. package/dist/agents/planner-prompts.js.map +1 -0
  118. package/dist/agents/planner.d.ts +90 -0
  119. package/dist/agents/planner.d.ts.map +1 -0
  120. package/dist/agents/planner.js +715 -0
  121. package/dist/agents/planner.js.map +1 -0
  122. package/dist/agents/polisher.d.ts +33 -0
  123. package/dist/agents/polisher.d.ts.map +1 -0
  124. package/dist/agents/polisher.js +122 -0
  125. package/dist/agents/polisher.js.map +1 -0
  126. package/dist/agents/post-write-validator.d.ts +35 -0
  127. package/dist/agents/post-write-validator.d.ts.map +1 -0
  128. package/dist/agents/post-write-validator.js +758 -0
  129. package/dist/agents/post-write-validator.js.map +1 -0
  130. package/dist/agents/radar-source.d.ts +38 -0
  131. package/dist/agents/radar-source.d.ts.map +1 -0
  132. package/dist/agents/radar-source.js +92 -0
  133. package/dist/agents/radar-source.js.map +1 -0
  134. package/dist/agents/radar.d.ts +24 -0
  135. package/dist/agents/radar.d.ts.map +1 -0
  136. package/dist/agents/radar.js +85 -0
  137. package/dist/agents/radar.js.map +1 -0
  138. package/dist/agents/researcher.d.ts +38 -0
  139. package/dist/agents/researcher.d.ts.map +1 -0
  140. package/dist/agents/researcher.js +164 -0
  141. package/dist/agents/researcher.js.map +1 -0
  142. package/dist/agents/reviser.d.ts +35 -0
  143. package/dist/agents/reviser.d.ts.map +1 -0
  144. package/dist/agents/reviser.js +496 -0
  145. package/dist/agents/reviser.js.map +1 -0
  146. package/dist/agents/rules-reader.d.ts +32 -0
  147. package/dist/agents/rules-reader.d.ts.map +1 -0
  148. package/dist/agents/rules-reader.js +129 -0
  149. package/dist/agents/rules-reader.js.map +1 -0
  150. package/dist/agents/script-storyboard.d.ts +66 -0
  151. package/dist/agents/script-storyboard.d.ts.map +1 -0
  152. package/dist/agents/script-storyboard.js +657 -0
  153. package/dist/agents/script-storyboard.js.map +1 -0
  154. package/dist/agents/sensitive-words.d.ts +24 -0
  155. package/dist/agents/sensitive-words.d.ts.map +1 -0
  156. package/dist/agents/sensitive-words.js +103 -0
  157. package/dist/agents/sensitive-words.js.map +1 -0
  158. package/dist/agents/settler-delta-parser.d.ts +7 -0
  159. package/dist/agents/settler-delta-parser.d.ts.map +1 -0
  160. package/dist/agents/settler-delta-parser.js +40 -0
  161. package/dist/agents/settler-delta-parser.js.map +1 -0
  162. package/dist/agents/settler-parser.d.ts +13 -0
  163. package/dist/agents/settler-parser.d.ts.map +1 -0
  164. package/dist/agents/settler-parser.js +20 -0
  165. package/dist/agents/settler-parser.js.map +1 -0
  166. package/dist/agents/settler-prompts.d.ts +22 -0
  167. package/dist/agents/settler-prompts.d.ts.map +1 -0
  168. package/dist/agents/settler-prompts.js +195 -0
  169. package/dist/agents/settler-prompts.js.map +1 -0
  170. package/dist/agents/short-fiction.d.ts +120 -0
  171. package/dist/agents/short-fiction.d.ts.map +1 -0
  172. package/dist/agents/short-fiction.js +353 -0
  173. package/dist/agents/short-fiction.js.map +1 -0
  174. package/dist/agents/state-validator.d.ts +33 -0
  175. package/dist/agents/state-validator.d.ts.map +1 -0
  176. package/dist/agents/state-validator.js +262 -0
  177. package/dist/agents/state-validator.js.map +1 -0
  178. package/dist/agents/style-analyzer.d.ts +11 -0
  179. package/dist/agents/style-analyzer.d.ts.map +1 -0
  180. package/dist/agents/style-analyzer.js +98 -0
  181. package/dist/agents/style-analyzer.js.map +1 -0
  182. package/dist/agents/writer-parser.d.ts +17 -0
  183. package/dist/agents/writer-parser.d.ts.map +1 -0
  184. package/dist/agents/writer-parser.js +131 -0
  185. package/dist/agents/writer-parser.js.map +1 -0
  186. package/dist/agents/writer-prompts.d.ts +12 -0
  187. package/dist/agents/writer-prompts.d.ts.map +1 -0
  188. package/dist/agents/writer-prompts.js +495 -0
  189. package/dist/agents/writer-prompts.js.map +1 -0
  190. package/dist/agents/writer.d.ts +102 -0
  191. package/dist/agents/writer.d.ts.map +1 -0
  192. package/dist/agents/writer.js +882 -0
  193. package/dist/agents/writer.js.map +1 -0
  194. package/dist/forecast/agent.d.ts +22 -0
  195. package/dist/forecast/agent.d.ts.map +1 -0
  196. package/dist/forecast/agent.js +48 -0
  197. package/dist/forecast/agent.js.map +1 -0
  198. package/dist/forecast/context-builder.d.ts +35 -0
  199. package/dist/forecast/context-builder.d.ts.map +1 -0
  200. package/dist/forecast/context-builder.js +214 -0
  201. package/dist/forecast/context-builder.js.map +1 -0
  202. package/dist/forecast/prompts.d.ts +12 -0
  203. package/dist/forecast/prompts.d.ts.map +1 -0
  204. package/dist/forecast/prompts.js +114 -0
  205. package/dist/forecast/prompts.js.map +1 -0
  206. package/dist/forecast/render.d.ts +9 -0
  207. package/dist/forecast/render.d.ts.map +1 -0
  208. package/dist/forecast/render.js +154 -0
  209. package/dist/forecast/render.js.map +1 -0
  210. package/dist/forecast/runner.d.ts +51 -0
  211. package/dist/forecast/runner.d.ts.map +1 -0
  212. package/dist/forecast/runner.js +115 -0
  213. package/dist/forecast/runner.js.map +1 -0
  214. package/dist/forecast/schema.d.ts +767 -0
  215. package/dist/forecast/schema.d.ts.map +1 -0
  216. package/dist/forecast/schema.js +115 -0
  217. package/dist/forecast/schema.js.map +1 -0
  218. package/dist/forecast/store.d.ts +32 -0
  219. package/dist/forecast/store.d.ts.map +1 -0
  220. package/dist/forecast/store.js +129 -0
  221. package/dist/forecast/store.js.map +1 -0
  222. package/dist/index.d.ts +160 -0
  223. package/dist/index.d.ts.map +1 -0
  224. package/dist/index.js +172 -0
  225. package/dist/index.js.map +1 -0
  226. package/dist/interaction/action-envelope.d.ts +1493 -0
  227. package/dist/interaction/action-envelope.d.ts.map +1 -0
  228. package/dist/interaction/action-envelope.js +257 -0
  229. package/dist/interaction/action-envelope.js.map +1 -0
  230. package/dist/interaction/book-session-store.d.ts +29 -0
  231. package/dist/interaction/book-session-store.d.ts.map +1 -0
  232. package/dist/interaction/book-session-store.js +185 -0
  233. package/dist/interaction/book-session-store.js.map +1 -0
  234. package/dist/interaction/draft-directive-parser.d.ts +38 -0
  235. package/dist/interaction/draft-directive-parser.d.ts.map +1 -0
  236. package/dist/interaction/draft-directive-parser.js +208 -0
  237. package/dist/interaction/draft-directive-parser.js.map +1 -0
  238. package/dist/interaction/edit-controller.d.ts +62 -0
  239. package/dist/interaction/edit-controller.d.ts.map +1 -0
  240. package/dist/interaction/edit-controller.js +381 -0
  241. package/dist/interaction/edit-controller.js.map +1 -0
  242. package/dist/interaction/events.d.ts +45 -0
  243. package/dist/interaction/events.d.ts.map +1 -0
  244. package/dist/interaction/events.js +32 -0
  245. package/dist/interaction/events.js.map +1 -0
  246. package/dist/interaction/export-artifact.d.ts +32 -0
  247. package/dist/interaction/export-artifact.d.ts.map +1 -0
  248. package/dist/interaction/export-artifact.js +103 -0
  249. package/dist/interaction/export-artifact.js.map +1 -0
  250. package/dist/interaction/intents.d.ts +99 -0
  251. package/dist/interaction/intents.d.ts.map +1 -0
  252. package/dist/interaction/intents.js +59 -0
  253. package/dist/interaction/intents.js.map +1 -0
  254. package/dist/interaction/modes.d.ts +5 -0
  255. package/dist/interaction/modes.d.ts.map +1 -0
  256. package/dist/interaction/modes.js +7 -0
  257. package/dist/interaction/modes.js.map +1 -0
  258. package/dist/interaction/project-control.d.ts +45 -0
  259. package/dist/interaction/project-control.d.ts.map +1 -0
  260. package/dist/interaction/project-control.js +71 -0
  261. package/dist/interaction/project-control.js.map +1 -0
  262. package/dist/interaction/project-session-store.d.ts +9 -0
  263. package/dist/interaction/project-session-store.d.ts.map +1 -0
  264. package/dist/interaction/project-session-store.js +64 -0
  265. package/dist/interaction/project-session-store.js.map +1 -0
  266. package/dist/interaction/project-tools.d.ts +23 -0
  267. package/dist/interaction/project-tools.d.ts.map +1 -0
  268. package/dist/interaction/project-tools.js +408 -0
  269. package/dist/interaction/project-tools.js.map +1 -0
  270. package/dist/interaction/request-router.d.ts +3 -0
  271. package/dist/interaction/request-router.d.ts.map +1 -0
  272. package/dist/interaction/request-router.js +5 -0
  273. package/dist/interaction/request-router.js.map +1 -0
  274. package/dist/interaction/runtime.d.ts +54 -0
  275. package/dist/interaction/runtime.d.ts.map +1 -0
  276. package/dist/interaction/runtime.js +919 -0
  277. package/dist/interaction/runtime.js.map +1 -0
  278. package/dist/interaction/session-transcript-legacy.d.ts +4 -0
  279. package/dist/interaction/session-transcript-legacy.d.ts.map +1 -0
  280. package/dist/interaction/session-transcript-legacy.js +102 -0
  281. package/dist/interaction/session-transcript-legacy.js.map +1 -0
  282. package/dist/interaction/session-transcript-restore.d.ts +18 -0
  283. package/dist/interaction/session-transcript-restore.d.ts.map +1 -0
  284. package/dist/interaction/session-transcript-restore.js +746 -0
  285. package/dist/interaction/session-transcript-restore.js.map +1 -0
  286. package/dist/interaction/session-transcript-schema.d.ts +435 -0
  287. package/dist/interaction/session-transcript-schema.d.ts.map +1 -0
  288. package/dist/interaction/session-transcript-schema.js +65 -0
  289. package/dist/interaction/session-transcript-schema.js.map +1 -0
  290. package/dist/interaction/session-transcript.d.ts +21 -0
  291. package/dist/interaction/session-transcript.d.ts.map +1 -0
  292. package/dist/interaction/session-transcript.js +164 -0
  293. package/dist/interaction/session-transcript.js.map +1 -0
  294. package/dist/interaction/session.d.ts +1194 -0
  295. package/dist/interaction/session.d.ts.map +1 -0
  296. package/dist/interaction/session.js +194 -0
  297. package/dist/interaction/session.js.map +1 -0
  298. package/dist/interaction/truth-authority.d.ts +4 -0
  299. package/dist/interaction/truth-authority.d.ts.map +1 -0
  300. package/dist/interaction/truth-authority.js +37 -0
  301. package/dist/interaction/truth-authority.js.map +1 -0
  302. package/dist/interactive-film/authoring-store.d.ts +35 -0
  303. package/dist/interactive-film/authoring-store.d.ts.map +1 -0
  304. package/dist/interactive-film/authoring-store.js +114 -0
  305. package/dist/interactive-film/authoring-store.js.map +1 -0
  306. package/dist/interactive-film/authoring-tools.d.ts +9 -0
  307. package/dist/interactive-film/authoring-tools.d.ts.map +1 -0
  308. package/dist/interactive-film/authoring-tools.js +19 -0
  309. package/dist/interactive-film/authoring-tools.js.map +1 -0
  310. package/dist/interactive-film/delta.d.ts +591 -0
  311. package/dist/interactive-film/delta.d.ts.map +1 -0
  312. package/dist/interactive-film/delta.js +54 -0
  313. package/dist/interactive-film/delta.js.map +1 -0
  314. package/dist/interactive-film/emotion.d.ts +20 -0
  315. package/dist/interactive-film/emotion.d.ts.map +1 -0
  316. package/dist/interactive-film/emotion.js +57 -0
  317. package/dist/interactive-film/emotion.js.map +1 -0
  318. package/dist/interactive-film/evaluator.d.ts +7 -0
  319. package/dist/interactive-film/evaluator.d.ts.map +1 -0
  320. package/dist/interactive-film/evaluator.js +40 -0
  321. package/dist/interactive-film/evaluator.js.map +1 -0
  322. package/dist/interactive-film/export-html.d.ts +6 -0
  323. package/dist/interactive-film/export-html.d.ts.map +1 -0
  324. package/dist/interactive-film/export-html.js +84 -0
  325. package/dist/interactive-film/export-html.js.map +1 -0
  326. package/dist/interactive-film/export-ink.d.ts +3 -0
  327. package/dist/interactive-film/export-ink.d.ts.map +1 -0
  328. package/dist/interactive-film/export-ink.js +51 -0
  329. package/dist/interactive-film/export-ink.js.map +1 -0
  330. package/dist/interactive-film/film-context.d.ts +4 -0
  331. package/dist/interactive-film/film-context.d.ts.map +1 -0
  332. package/dist/interactive-film/film-context.js +30 -0
  333. package/dist/interactive-film/film-context.js.map +1 -0
  334. package/dist/interactive-film/generate.d.ts +15 -0
  335. package/dist/interactive-film/generate.d.ts.map +1 -0
  336. package/dist/interactive-film/generate.js +51 -0
  337. package/dist/interactive-film/generate.js.map +1 -0
  338. package/dist/interactive-film/graph-schema.d.ts +819 -0
  339. package/dist/interactive-film/graph-schema.d.ts.map +1 -0
  340. package/dist/interactive-film/graph-schema.js +84 -0
  341. package/dist/interactive-film/graph-schema.js.map +1 -0
  342. package/dist/interactive-film/graph-store.d.ts +5 -0
  343. package/dist/interactive-film/graph-store.d.ts.map +1 -0
  344. package/dist/interactive-film/graph-store.js +24 -0
  345. package/dist/interactive-film/graph-store.js.map +1 -0
  346. package/dist/interactive-film/memory-link.d.ts +9 -0
  347. package/dist/interactive-film/memory-link.d.ts.map +1 -0
  348. package/dist/interactive-film/memory-link.js +15 -0
  349. package/dist/interactive-film/memory-link.js.map +1 -0
  350. package/dist/interactive-film/node-image.d.ts +29 -0
  351. package/dist/interactive-film/node-image.d.ts.map +1 -0
  352. package/dist/interactive-film/node-image.js +52 -0
  353. package/dist/interactive-film/node-image.js.map +1 -0
  354. package/dist/interactive-film/paths.d.ts +14 -0
  355. package/dist/interactive-film/paths.d.ts.map +1 -0
  356. package/dist/interactive-film/paths.js +60 -0
  357. package/dist/interactive-film/paths.js.map +1 -0
  358. package/dist/interactive-film/tool-schemas.d.ts +182 -0
  359. package/dist/interactive-film/tool-schemas.d.ts.map +1 -0
  360. package/dist/interactive-film/tool-schemas.js +125 -0
  361. package/dist/interactive-film/tool-schemas.js.map +1 -0
  362. package/dist/interactive-film/validation.d.ts +14 -0
  363. package/dist/interactive-film/validation.d.ts.map +1 -0
  364. package/dist/interactive-film/validation.js +292 -0
  365. package/dist/interactive-film/validation.js.map +1 -0
  366. package/dist/llm/agent-trajectory.d.ts +27 -0
  367. package/dist/llm/agent-trajectory.d.ts.map +1 -0
  368. package/dist/llm/agent-trajectory.js +74 -0
  369. package/dist/llm/agent-trajectory.js.map +1 -0
  370. package/dist/llm/config-migration.d.ts +5 -0
  371. package/dist/llm/config-migration.d.ts.map +1 -0
  372. package/dist/llm/config-migration.js +51 -0
  373. package/dist/llm/config-migration.js.map +1 -0
  374. package/dist/llm/cover-providers.d.ts +14 -0
  375. package/dist/llm/cover-providers.d.ts.map +1 -0
  376. package/dist/llm/cover-providers.js +51 -0
  377. package/dist/llm/cover-providers.js.map +1 -0
  378. package/dist/llm/long-form-completion.d.ts +16 -0
  379. package/dist/llm/long-form-completion.d.ts.map +1 -0
  380. package/dist/llm/long-form-completion.js +62 -0
  381. package/dist/llm/long-form-completion.js.map +1 -0
  382. package/dist/llm/provider.d.ts +110 -0
  383. package/dist/llm/provider.d.ts.map +1 -0
  384. package/dist/llm/provider.js +1405 -0
  385. package/dist/llm/provider.js.map +1 -0
  386. package/dist/llm/providers/endpoints/ai360.d.ts +10 -0
  387. package/dist/llm/providers/endpoints/ai360.d.ts.map +1 -0
  388. package/dist/llm/providers/endpoints/ai360.js +34 -0
  389. package/dist/llm/providers/endpoints/ai360.js.map +1 -0
  390. package/dist/llm/providers/endpoints/anthropic.d.ts +12 -0
  391. package/dist/llm/providers/endpoints/anthropic.d.ts.map +1 -0
  392. package/dist/llm/providers/endpoints/anthropic.js +72 -0
  393. package/dist/llm/providers/endpoints/anthropic.js.map +1 -0
  394. package/dist/llm/providers/endpoints/astronCodingPlan.d.ts +16 -0
  395. package/dist/llm/providers/endpoints/astronCodingPlan.d.ts.map +1 -0
  396. package/dist/llm/providers/endpoints/astronCodingPlan.js +16 -0
  397. package/dist/llm/providers/endpoints/astronCodingPlan.js.map +1 -0
  398. package/dist/llm/providers/endpoints/baichuan.d.ts +10 -0
  399. package/dist/llm/providers/endpoints/baichuan.d.ts.map +1 -0
  400. package/dist/llm/providers/endpoints/baichuan.js +20 -0
  401. package/dist/llm/providers/endpoints/baichuan.js.map +1 -0
  402. package/dist/llm/providers/endpoints/bailian.d.ts +25 -0
  403. package/dist/llm/providers/endpoints/bailian.d.ts.map +1 -0
  404. package/dist/llm/providers/endpoints/bailian.js +42 -0
  405. package/dist/llm/providers/endpoints/bailian.js.map +1 -0
  406. package/dist/llm/providers/endpoints/bailianCodingPlan.d.ts +10 -0
  407. package/dist/llm/providers/endpoints/bailianCodingPlan.d.ts.map +1 -0
  408. package/dist/llm/providers/endpoints/bailianCodingPlan.js +22 -0
  409. package/dist/llm/providers/endpoints/bailianCodingPlan.js.map +1 -0
  410. package/dist/llm/providers/endpoints/custom.d.ts +16 -0
  411. package/dist/llm/providers/endpoints/custom.d.ts.map +1 -0
  412. package/dist/llm/providers/endpoints/custom.js +15 -0
  413. package/dist/llm/providers/endpoints/custom.js.map +1 -0
  414. package/dist/llm/providers/endpoints/deepseek.d.ts +17 -0
  415. package/dist/llm/providers/endpoints/deepseek.d.ts.map +1 -0
  416. package/dist/llm/providers/endpoints/deepseek.js +20 -0
  417. package/dist/llm/providers/endpoints/deepseek.js.map +1 -0
  418. package/dist/llm/providers/endpoints/giteeai.d.ts +10 -0
  419. package/dist/llm/providers/endpoints/giteeai.d.ts.map +1 -0
  420. package/dist/llm/providers/endpoints/giteeai.js +33 -0
  421. package/dist/llm/providers/endpoints/giteeai.js.map +1 -0
  422. package/dist/llm/providers/endpoints/githubCopilot.d.ts +10 -0
  423. package/dist/llm/providers/endpoints/githubCopilot.d.ts.map +1 -0
  424. package/dist/llm/providers/endpoints/githubCopilot.js +35 -0
  425. package/dist/llm/providers/endpoints/githubCopilot.js.map +1 -0
  426. package/dist/llm/providers/endpoints/glmCodingPlan.d.ts +9 -0
  427. package/dist/llm/providers/endpoints/glmCodingPlan.d.ts.map +1 -0
  428. package/dist/llm/providers/endpoints/glmCodingPlan.js +21 -0
  429. package/dist/llm/providers/endpoints/glmCodingPlan.js.map +1 -0
  430. package/dist/llm/providers/endpoints/google.d.ts +12 -0
  431. package/dist/llm/providers/endpoints/google.d.ts.map +1 -0
  432. package/dist/llm/providers/endpoints/google.js +41 -0
  433. package/dist/llm/providers/endpoints/google.js.map +1 -0
  434. package/dist/llm/providers/endpoints/hunyuan.d.ts +10 -0
  435. package/dist/llm/providers/endpoints/hunyuan.d.ts.map +1 -0
  436. package/dist/llm/providers/endpoints/hunyuan.js +34 -0
  437. package/dist/llm/providers/endpoints/hunyuan.js.map +1 -0
  438. package/dist/llm/providers/endpoints/infiniai.d.ts +10 -0
  439. package/dist/llm/providers/endpoints/infiniai.d.ts.map +1 -0
  440. package/dist/llm/providers/endpoints/infiniai.js +64 -0
  441. package/dist/llm/providers/endpoints/infiniai.js.map +1 -0
  442. package/dist/llm/providers/endpoints/internlm.d.ts +10 -0
  443. package/dist/llm/providers/endpoints/internlm.d.ts.map +1 -0
  444. package/dist/llm/providers/endpoints/internlm.js +20 -0
  445. package/dist/llm/providers/endpoints/internlm.js.map +1 -0
  446. package/dist/llm/providers/endpoints/kimiCode.d.ts +9 -0
  447. package/dist/llm/providers/endpoints/kimiCode.d.ts.map +1 -0
  448. package/dist/llm/providers/endpoints/kimiCode.js +16 -0
  449. package/dist/llm/providers/endpoints/kimiCode.js.map +1 -0
  450. package/dist/llm/providers/endpoints/kimiCodingPlan.d.ts +9 -0
  451. package/dist/llm/providers/endpoints/kimiCodingPlan.d.ts.map +1 -0
  452. package/dist/llm/providers/endpoints/kimiCodingPlan.js +17 -0
  453. package/dist/llm/providers/endpoints/kimiCodingPlan.js.map +1 -0
  454. package/dist/llm/providers/endpoints/kkaiapi.d.ts +14 -0
  455. package/dist/llm/providers/endpoints/kkaiapi.d.ts.map +1 -0
  456. package/dist/llm/providers/endpoints/kkaiapi.js +44 -0
  457. package/dist/llm/providers/endpoints/kkaiapi.js.map +1 -0
  458. package/dist/llm/providers/endpoints/lmstudio.d.ts +10 -0
  459. package/dist/llm/providers/endpoints/lmstudio.d.ts.map +1 -0
  460. package/dist/llm/providers/endpoints/lmstudio.js +15 -0
  461. package/dist/llm/providers/endpoints/lmstudio.js.map +1 -0
  462. package/dist/llm/providers/endpoints/longcat.d.ts +9 -0
  463. package/dist/llm/providers/endpoints/longcat.d.ts.map +1 -0
  464. package/dist/llm/providers/endpoints/longcat.js +18 -0
  465. package/dist/llm/providers/endpoints/longcat.js.map +1 -0
  466. package/dist/llm/providers/endpoints/minimax.d.ts +15 -0
  467. package/dist/llm/providers/endpoints/minimax.d.ts.map +1 -0
  468. package/dist/llm/providers/endpoints/minimax.js +27 -0
  469. package/dist/llm/providers/endpoints/minimax.js.map +1 -0
  470. package/dist/llm/providers/endpoints/minimaxCodingPlan.d.ts +9 -0
  471. package/dist/llm/providers/endpoints/minimaxCodingPlan.d.ts.map +1 -0
  472. package/dist/llm/providers/endpoints/minimaxCodingPlan.js +21 -0
  473. package/dist/llm/providers/endpoints/minimaxCodingPlan.js.map +1 -0
  474. package/dist/llm/providers/endpoints/mistral.d.ts +11 -0
  475. package/dist/llm/providers/endpoints/mistral.d.ts.map +1 -0
  476. package/dist/llm/providers/endpoints/mistral.js +31 -0
  477. package/dist/llm/providers/endpoints/mistral.js.map +1 -0
  478. package/dist/llm/providers/endpoints/modelscope.d.ts +10 -0
  479. package/dist/llm/providers/endpoints/modelscope.d.ts.map +1 -0
  480. package/dist/llm/providers/endpoints/modelscope.js +22 -0
  481. package/dist/llm/providers/endpoints/modelscope.js.map +1 -0
  482. package/dist/llm/providers/endpoints/moonshot.d.ts +12 -0
  483. package/dist/llm/providers/endpoints/moonshot.d.ts.map +1 -0
  484. package/dist/llm/providers/endpoints/moonshot.js +29 -0
  485. package/dist/llm/providers/endpoints/moonshot.js.map +1 -0
  486. package/dist/llm/providers/endpoints/newapi.d.ts +14 -0
  487. package/dist/llm/providers/endpoints/newapi.d.ts.map +1 -0
  488. package/dist/llm/providers/endpoints/newapi.js +14 -0
  489. package/dist/llm/providers/endpoints/newapi.js.map +1 -0
  490. package/dist/llm/providers/endpoints/ollama.d.ts +12 -0
  491. package/dist/llm/providers/endpoints/ollama.d.ts.map +1 -0
  492. package/dist/llm/providers/endpoints/ollama.js +63 -0
  493. package/dist/llm/providers/endpoints/ollama.js.map +1 -0
  494. package/dist/llm/providers/endpoints/openai.d.ts +12 -0
  495. package/dist/llm/providers/endpoints/openai.d.ts.map +1 -0
  496. package/dist/llm/providers/endpoints/openai.js +67 -0
  497. package/dist/llm/providers/endpoints/openai.js.map +1 -0
  498. package/dist/llm/providers/endpoints/opencodeCodingPlan.d.ts +9 -0
  499. package/dist/llm/providers/endpoints/opencodeCodingPlan.d.ts.map +1 -0
  500. package/dist/llm/providers/endpoints/opencodeCodingPlan.js +23 -0
  501. package/dist/llm/providers/endpoints/opencodeCodingPlan.js.map +1 -0
  502. package/dist/llm/providers/endpoints/openrouter.d.ts +15 -0
  503. package/dist/llm/providers/endpoints/openrouter.d.ts.map +1 -0
  504. package/dist/llm/providers/endpoints/openrouter.js +75 -0
  505. package/dist/llm/providers/endpoints/openrouter.js.map +1 -0
  506. package/dist/llm/providers/endpoints/ppio.d.ts +15 -0
  507. package/dist/llm/providers/endpoints/ppio.d.ts.map +1 -0
  508. package/dist/llm/providers/endpoints/ppio.js +73 -0
  509. package/dist/llm/providers/endpoints/ppio.js.map +1 -0
  510. package/dist/llm/providers/endpoints/qiniu.d.ts +10 -0
  511. package/dist/llm/providers/endpoints/qiniu.d.ts.map +1 -0
  512. package/dist/llm/providers/endpoints/qiniu.js +24 -0
  513. package/dist/llm/providers/endpoints/qiniu.js.map +1 -0
  514. package/dist/llm/providers/endpoints/sensenova.d.ts +10 -0
  515. package/dist/llm/providers/endpoints/sensenova.d.ts.map +1 -0
  516. package/dist/llm/providers/endpoints/sensenova.js +37 -0
  517. package/dist/llm/providers/endpoints/sensenova.js.map +1 -0
  518. package/dist/llm/providers/endpoints/siliconcloud.d.ts +14 -0
  519. package/dist/llm/providers/endpoints/siliconcloud.d.ts.map +1 -0
  520. package/dist/llm/providers/endpoints/siliconcloud.js +114 -0
  521. package/dist/llm/providers/endpoints/siliconcloud.js.map +1 -0
  522. package/dist/llm/providers/endpoints/spark.d.ts +14 -0
  523. package/dist/llm/providers/endpoints/spark.d.ts.map +1 -0
  524. package/dist/llm/providers/endpoints/spark.js +21 -0
  525. package/dist/llm/providers/endpoints/spark.js.map +1 -0
  526. package/dist/llm/providers/endpoints/stepfun.d.ts +10 -0
  527. package/dist/llm/providers/endpoints/stepfun.d.ts.map +1 -0
  528. package/dist/llm/providers/endpoints/stepfun.js +27 -0
  529. package/dist/llm/providers/endpoints/stepfun.js.map +1 -0
  530. package/dist/llm/providers/endpoints/tencentcloud.d.ts +10 -0
  531. package/dist/llm/providers/endpoints/tencentcloud.d.ts.map +1 -0
  532. package/dist/llm/providers/endpoints/tencentcloud.js +17 -0
  533. package/dist/llm/providers/endpoints/tencentcloud.js.map +1 -0
  534. package/dist/llm/providers/endpoints/volcengine.d.ts +10 -0
  535. package/dist/llm/providers/endpoints/volcengine.d.ts.map +1 -0
  536. package/dist/llm/providers/endpoints/volcengine.js +44 -0
  537. package/dist/llm/providers/endpoints/volcengine.js.map +1 -0
  538. package/dist/llm/providers/endpoints/volcengineCodingPlan.d.ts +19 -0
  539. package/dist/llm/providers/endpoints/volcengineCodingPlan.d.ts.map +1 -0
  540. package/dist/llm/providers/endpoints/volcengineCodingPlan.js +25 -0
  541. package/dist/llm/providers/endpoints/volcengineCodingPlan.js.map +1 -0
  542. package/dist/llm/providers/endpoints/wenxin.d.ts +10 -0
  543. package/dist/llm/providers/endpoints/wenxin.d.ts.map +1 -0
  544. package/dist/llm/providers/endpoints/wenxin.js +98 -0
  545. package/dist/llm/providers/endpoints/wenxin.js.map +1 -0
  546. package/dist/llm/providers/endpoints/xai.d.ts +11 -0
  547. package/dist/llm/providers/endpoints/xai.d.ts.map +1 -0
  548. package/dist/llm/providers/endpoints/xai.js +25 -0
  549. package/dist/llm/providers/endpoints/xai.js.map +1 -0
  550. package/dist/llm/providers/endpoints/xiaomimimo.d.ts +12 -0
  551. package/dist/llm/providers/endpoints/xiaomimimo.d.ts.map +1 -0
  552. package/dist/llm/providers/endpoints/xiaomimimo.js +16 -0
  553. package/dist/llm/providers/endpoints/xiaomimimo.js.map +1 -0
  554. package/dist/llm/providers/endpoints/zeroone.d.ts +10 -0
  555. package/dist/llm/providers/endpoints/zeroone.d.ts.map +1 -0
  556. package/dist/llm/providers/endpoints/zeroone.js +26 -0
  557. package/dist/llm/providers/endpoints/zeroone.js.map +1 -0
  558. package/dist/llm/providers/endpoints/zhipu.d.ts +12 -0
  559. package/dist/llm/providers/endpoints/zhipu.d.ts.map +1 -0
  560. package/dist/llm/providers/endpoints/zhipu.js +51 -0
  561. package/dist/llm/providers/endpoints/zhipu.js.map +1 -0
  562. package/dist/llm/providers/index.d.ts +5 -0
  563. package/dist/llm/providers/index.d.ts.map +1 -0
  564. package/dist/llm/providers/index.js +64 -0
  565. package/dist/llm/providers/index.js.map +1 -0
  566. package/dist/llm/providers/lookup.d.ts +16 -0
  567. package/dist/llm/providers/lookup.d.ts.map +1 -0
  568. package/dist/llm/providers/lookup.js +68 -0
  569. package/dist/llm/providers/lookup.js.map +1 -0
  570. package/dist/llm/providers/probe.d.ts +11 -0
  571. package/dist/llm/providers/probe.d.ts.map +1 -0
  572. package/dist/llm/providers/probe.js +24 -0
  573. package/dist/llm/providers/probe.js.map +1 -0
  574. package/dist/llm/providers/types.d.ts +71 -0
  575. package/dist/llm/providers/types.d.ts.map +1 -0
  576. package/dist/llm/providers/types.js +9 -0
  577. package/dist/llm/providers/types.js.map +1 -0
  578. package/dist/llm/providers/verify.d.ts +27 -0
  579. package/dist/llm/providers/verify.d.ts.map +1 -0
  580. package/dist/llm/providers/verify.js +77 -0
  581. package/dist/llm/providers/verify.js.map +1 -0
  582. package/dist/llm/secrets.d.ts +9 -0
  583. package/dist/llm/secrets.d.ts.map +1 -0
  584. package/dist/llm/secrets.js +56 -0
  585. package/dist/llm/secrets.js.map +1 -0
  586. package/dist/llm/service-presets.d.ts +45 -0
  587. package/dist/llm/service-presets.d.ts.map +1 -0
  588. package/dist/llm/service-presets.js +180 -0
  589. package/dist/llm/service-presets.js.map +1 -0
  590. package/dist/llm/service-resolver.d.ts +10 -0
  591. package/dist/llm/service-resolver.d.ts.map +1 -0
  592. package/dist/llm/service-resolver.js +62 -0
  593. package/dist/llm/service-resolver.js.map +1 -0
  594. package/dist/llm/think-tag-stripper.d.ts +15 -0
  595. package/dist/llm/think-tag-stripper.d.ts.map +1 -0
  596. package/dist/llm/think-tag-stripper.js +60 -0
  597. package/dist/llm/think-tag-stripper.js.map +1 -0
  598. package/dist/materials/ingest.d.ts +31 -0
  599. package/dist/materials/ingest.d.ts.map +1 -0
  600. package/dist/materials/ingest.js +224 -0
  601. package/dist/materials/ingest.js.map +1 -0
  602. package/dist/materials/retrieve.d.ts +20 -0
  603. package/dist/materials/retrieve.d.ts.map +1 -0
  604. package/dist/materials/retrieve.js +109 -0
  605. package/dist/materials/retrieve.js.map +1 -0
  606. package/dist/models/book-rules.d.ts +142 -0
  607. package/dist/models/book-rules.d.ts.map +1 -0
  608. package/dist/models/book-rules.js +286 -0
  609. package/dist/models/book-rules.js.map +1 -0
  610. package/dist/models/book.d.ts +94 -0
  611. package/dist/models/book.d.ts.map +1 -0
  612. package/dist/models/book.js +79 -0
  613. package/dist/models/book.js.map +1 -0
  614. package/dist/models/chapter.d.ts +131 -0
  615. package/dist/models/chapter.d.ts.map +1 -0
  616. package/dist/models/chapter.js +38 -0
  617. package/dist/models/chapter.js.map +1 -0
  618. package/dist/models/context-compression.d.ts +13 -0
  619. package/dist/models/context-compression.d.ts.map +1 -0
  620. package/dist/models/context-compression.js +2 -0
  621. package/dist/models/context-compression.js.map +1 -0
  622. package/dist/models/detection.d.ts +25 -0
  623. package/dist/models/detection.d.ts.map +1 -0
  624. package/dist/models/detection.js +2 -0
  625. package/dist/models/detection.js.map +1 -0
  626. package/dist/models/genre-profile.d.ts +45 -0
  627. package/dist/models/genre-profile.d.ts.map +1 -0
  628. package/dist/models/genre-profile.js +26 -0
  629. package/dist/models/genre-profile.js.map +1 -0
  630. package/dist/models/input-governance.d.ts +437 -0
  631. package/dist/models/input-governance.d.ts.map +1 -0
  632. package/dist/models/input-governance.js +104 -0
  633. package/dist/models/input-governance.js.map +1 -0
  634. package/dist/models/length-governance.d.ts +85 -0
  635. package/dist/models/length-governance.d.ts.map +1 -0
  636. package/dist/models/length-governance.js +31 -0
  637. package/dist/models/length-governance.js.map +1 -0
  638. package/dist/models/play.d.ts +530 -0
  639. package/dist/models/play.d.ts.map +1 -0
  640. package/dist/models/play.js +318 -0
  641. package/dist/models/play.js.map +1 -0
  642. package/dist/models/project.d.ts +797 -0
  643. package/dist/models/project.d.ts.map +1 -0
  644. package/dist/models/project.js +152 -0
  645. package/dist/models/project.js.map +1 -0
  646. package/dist/models/runtime-state.d.ts +708 -0
  647. package/dist/models/runtime-state.d.ts.map +1 -0
  648. package/dist/models/runtime-state.js +105 -0
  649. package/dist/models/runtime-state.js.map +1 -0
  650. package/dist/models/state.d.ts +48 -0
  651. package/dist/models/state.d.ts.map +1 -0
  652. package/dist/models/state.js +6 -0
  653. package/dist/models/state.js.map +1 -0
  654. package/dist/models/style-profile.d.ts +16 -0
  655. package/dist/models/style-profile.d.ts.map +1 -0
  656. package/dist/models/style-profile.js +2 -0
  657. package/dist/models/style-profile.js.map +1 -0
  658. package/dist/notify/dispatcher.d.ts +10 -0
  659. package/dist/notify/dispatcher.d.ts.map +1 -0
  660. package/dist/notify/dispatcher.js +67 -0
  661. package/dist/notify/dispatcher.js.map +1 -0
  662. package/dist/notify/feishu.d.ts +6 -0
  663. package/dist/notify/feishu.d.ts.map +1 -0
  664. package/dist/notify/feishu.js +32 -0
  665. package/dist/notify/feishu.js.map +1 -0
  666. package/dist/notify/format.d.ts +12 -0
  667. package/dist/notify/format.d.ts.map +1 -0
  668. package/dist/notify/format.js +11 -0
  669. package/dist/notify/format.js.map +1 -0
  670. package/dist/notify/telegram.d.ts +7 -0
  671. package/dist/notify/telegram.d.ts.map +1 -0
  672. package/dist/notify/telegram.js +18 -0
  673. package/dist/notify/telegram.js.map +1 -0
  674. package/dist/notify/webhook.d.ts +15 -0
  675. package/dist/notify/webhook.d.ts.map +1 -0
  676. package/dist/notify/webhook.js +28 -0
  677. package/dist/notify/webhook.js.map +1 -0
  678. package/dist/notify/wechat-work.d.ts +6 -0
  679. package/dist/notify/wechat-work.d.ts.map +1 -0
  680. package/dist/notify/wechat-work.js +15 -0
  681. package/dist/notify/wechat-work.js.map +1 -0
  682. package/dist/pipeline/chapter-persistence.d.ts +33 -0
  683. package/dist/pipeline/chapter-persistence.d.ts.map +1 -0
  684. package/dist/pipeline/chapter-persistence.js +39 -0
  685. package/dist/pipeline/chapter-persistence.js.map +1 -0
  686. package/dist/pipeline/chapter-review-cycle.d.ts +81 -0
  687. package/dist/pipeline/chapter-review-cycle.d.ts.map +1 -0
  688. package/dist/pipeline/chapter-review-cycle.js +190 -0
  689. package/dist/pipeline/chapter-review-cycle.js.map +1 -0
  690. package/dist/pipeline/chapter-state-recovery.d.ts +61 -0
  691. package/dist/pipeline/chapter-state-recovery.d.ts.map +1 -0
  692. package/dist/pipeline/chapter-state-recovery.js +135 -0
  693. package/dist/pipeline/chapter-state-recovery.js.map +1 -0
  694. package/dist/pipeline/chapter-truth-validation.d.ts +42 -0
  695. package/dist/pipeline/chapter-truth-validation.d.ts.map +1 -0
  696. package/dist/pipeline/chapter-truth-validation.js +93 -0
  697. package/dist/pipeline/chapter-truth-validation.js.map +1 -0
  698. package/dist/pipeline/detection-runner.d.ts +31 -0
  699. package/dist/pipeline/detection-runner.d.ts.map +1 -0
  700. package/dist/pipeline/detection-runner.js +109 -0
  701. package/dist/pipeline/detection-runner.js.map +1 -0
  702. package/dist/pipeline/persisted-governed-plan.d.ts +5 -0
  703. package/dist/pipeline/persisted-governed-plan.d.ts.map +1 -0
  704. package/dist/pipeline/persisted-governed-plan.js +249 -0
  705. package/dist/pipeline/persisted-governed-plan.js.map +1 -0
  706. package/dist/pipeline/runner.d.ts +334 -0
  707. package/dist/pipeline/runner.d.ts.map +1 -0
  708. package/dist/pipeline/runner.js +2974 -0
  709. package/dist/pipeline/runner.js.map +1 -0
  710. package/dist/pipeline/scheduler.d.ts +58 -0
  711. package/dist/pipeline/scheduler.d.ts.map +1 -0
  712. package/dist/pipeline/scheduler.js +322 -0
  713. package/dist/pipeline/scheduler.js.map +1 -0
  714. package/dist/pipeline/script-storyboard-runner.d.ts +130 -0
  715. package/dist/pipeline/script-storyboard-runner.d.ts.map +1 -0
  716. package/dist/pipeline/script-storyboard-runner.js +532 -0
  717. package/dist/pipeline/script-storyboard-runner.js.map +1 -0
  718. package/dist/pipeline/short-fiction-runner.d.ts +110 -0
  719. package/dist/pipeline/short-fiction-runner.d.ts.map +1 -0
  720. package/dist/pipeline/short-fiction-runner.js +864 -0
  721. package/dist/pipeline/short-fiction-runner.js.map +1 -0
  722. package/dist/play/play-agents.d.ts +72 -0
  723. package/dist/play/play-agents.d.ts.map +1 -0
  724. package/dist/play/play-agents.js +580 -0
  725. package/dist/play/play-agents.js.map +1 -0
  726. package/dist/play/play-db-factory.d.ts +9 -0
  727. package/dist/play/play-db-factory.d.ts.map +1 -0
  728. package/dist/play/play-db-factory.js +18 -0
  729. package/dist/play/play-db-factory.js.map +1 -0
  730. package/dist/play/play-db.d.ts +22 -0
  731. package/dist/play/play-db.d.ts.map +1 -0
  732. package/dist/play/play-db.js +248 -0
  733. package/dist/play/play-db.js.map +1 -0
  734. package/dist/play/play-file-db.d.ts +32 -0
  735. package/dist/play/play-file-db.d.ts.map +1 -0
  736. package/dist/play/play-file-db.js +156 -0
  737. package/dist/play/play-file-db.js.map +1 -0
  738. package/dist/play/play-image.d.ts +58 -0
  739. package/dist/play/play-image.d.ts.map +1 -0
  740. package/dist/play/play-image.js +142 -0
  741. package/dist/play/play-image.js.map +1 -0
  742. package/dist/play/play-reducer.d.ts +31 -0
  743. package/dist/play/play-reducer.d.ts.map +1 -0
  744. package/dist/play/play-reducer.js +261 -0
  745. package/dist/play/play-reducer.js.map +1 -0
  746. package/dist/play/play-runner.d.ts +116 -0
  747. package/dist/play/play-runner.d.ts.map +1 -0
  748. package/dist/play/play-runner.js +582 -0
  749. package/dist/play/play-runner.js.map +1 -0
  750. package/dist/play/play-store.d.ts +113 -0
  751. package/dist/play/play-store.d.ts.map +1 -0
  752. package/dist/play/play-store.js +314 -0
  753. package/dist/play/play-store.js.map +1 -0
  754. package/dist/production/harness.d.ts +57 -0
  755. package/dist/production/harness.d.ts.map +1 -0
  756. package/dist/production/harness.js +51 -0
  757. package/dist/production/harness.js.map +1 -0
  758. package/dist/prompts/builtin-prompts.d.ts +10 -0
  759. package/dist/prompts/builtin-prompts.d.ts.map +1 -0
  760. package/dist/prompts/builtin-prompts.js +157 -0
  761. package/dist/prompts/builtin-prompts.js.map +1 -0
  762. package/dist/prompts/index.d.ts +5 -0
  763. package/dist/prompts/index.d.ts.map +1 -0
  764. package/dist/prompts/index.js +5 -0
  765. package/dist/prompts/index.js.map +1 -0
  766. package/dist/prompts/prompt-pack.d.ts +33 -0
  767. package/dist/prompts/prompt-pack.d.ts.map +1 -0
  768. package/dist/prompts/prompt-pack.js +82 -0
  769. package/dist/prompts/prompt-pack.js.map +1 -0
  770. package/dist/prompts/short-fiction.d.ts +58 -0
  771. package/dist/prompts/short-fiction.d.ts.map +1 -0
  772. package/dist/prompts/short-fiction.js +480 -0
  773. package/dist/prompts/short-fiction.js.map +1 -0
  774. package/dist/prompts/types.d.ts +22 -0
  775. package/dist/prompts/types.d.ts.map +1 -0
  776. package/dist/prompts/types.js +9 -0
  777. package/dist/prompts/types.js.map +1 -0
  778. package/dist/references/book-references.d.ts +40 -0
  779. package/dist/references/book-references.d.ts.map +1 -0
  780. package/dist/references/book-references.js +207 -0
  781. package/dist/references/book-references.js.map +1 -0
  782. package/dist/references/reference-context.d.ts +26 -0
  783. package/dist/references/reference-context.d.ts.map +1 -0
  784. package/dist/references/reference-context.js +120 -0
  785. package/dist/references/reference-context.js.map +1 -0
  786. package/dist/retrieval/local-search.d.ts +38 -0
  787. package/dist/retrieval/local-search.d.ts.map +1 -0
  788. package/dist/retrieval/local-search.js +232 -0
  789. package/dist/retrieval/local-search.js.map +1 -0
  790. package/dist/skills/builtin-loader.d.ts +9 -0
  791. package/dist/skills/builtin-loader.d.ts.map +1 -0
  792. package/dist/skills/builtin-loader.js +24 -0
  793. package/dist/skills/builtin-loader.js.map +1 -0
  794. package/dist/skills/external-loader.d.ts +26 -0
  795. package/dist/skills/external-loader.d.ts.map +1 -0
  796. package/dist/skills/external-loader.js +186 -0
  797. package/dist/skills/external-loader.js.map +1 -0
  798. package/dist/skills/index.d.ts +6 -0
  799. package/dist/skills/index.d.ts.map +1 -0
  800. package/dist/skills/index.js +6 -0
  801. package/dist/skills/index.js.map +1 -0
  802. package/dist/skills/production-bindings.d.ts +18 -0
  803. package/dist/skills/production-bindings.d.ts.map +1 -0
  804. package/dist/skills/production-bindings.js +37 -0
  805. package/dist/skills/production-bindings.js.map +1 -0
  806. package/dist/skills/registry.d.ts +6 -0
  807. package/dist/skills/registry.d.ts.map +1 -0
  808. package/dist/skills/registry.js +68 -0
  809. package/dist/skills/registry.js.map +1 -0
  810. package/dist/skills/types.d.ts +42 -0
  811. package/dist/skills/types.d.ts.map +1 -0
  812. package/dist/skills/types.js +10 -0
  813. package/dist/skills/types.js.map +1 -0
  814. package/dist/state/chapter-delete.d.ts +30 -0
  815. package/dist/state/chapter-delete.d.ts.map +1 -0
  816. package/dist/state/chapter-delete.js +78 -0
  817. package/dist/state/chapter-delete.js.map +1 -0
  818. package/dist/state/chapter-word-sync.d.ts +30 -0
  819. package/dist/state/chapter-word-sync.d.ts.map +1 -0
  820. package/dist/state/chapter-word-sync.js +62 -0
  821. package/dist/state/chapter-word-sync.js.map +1 -0
  822. package/dist/state/chapter-workspace.d.ts +15 -0
  823. package/dist/state/chapter-workspace.d.ts.map +1 -0
  824. package/dist/state/chapter-workspace.js +118 -0
  825. package/dist/state/chapter-workspace.js.map +1 -0
  826. package/dist/state/manager.d.ts +70 -0
  827. package/dist/state/manager.d.ts.map +1 -0
  828. package/dist/state/manager.js +727 -0
  829. package/dist/state/manager.js.map +1 -0
  830. package/dist/state/memory-db.d.ts +83 -0
  831. package/dist/state/memory-db.d.ts.map +1 -0
  832. package/dist/state/memory-db.js +249 -0
  833. package/dist/state/memory-db.js.map +1 -0
  834. package/dist/state/runtime-state-store.d.ts +38 -0
  835. package/dist/state/runtime-state-store.d.ts.map +1 -0
  836. package/dist/state/runtime-state-store.js +164 -0
  837. package/dist/state/runtime-state-store.js.map +1 -0
  838. package/dist/state/state-bootstrap.d.ts +21 -0
  839. package/dist/state/state-bootstrap.d.ts.map +1 -0
  840. package/dist/state/state-bootstrap.js +476 -0
  841. package/dist/state/state-bootstrap.js.map +1 -0
  842. package/dist/state/state-projections.d.ts +7 -0
  843. package/dist/state/state-projections.d.ts.map +1 -0
  844. package/dist/state/state-projections.js +209 -0
  845. package/dist/state/state-projections.js.map +1 -0
  846. package/dist/state/state-reducer.d.ts +13 -0
  847. package/dist/state/state-reducer.d.ts.map +1 -0
  848. package/dist/state/state-reducer.js +193 -0
  849. package/dist/state/state-reducer.js.map +1 -0
  850. package/dist/state/state-validator.d.ts +12 -0
  851. package/dist/state/state-validator.d.ts.map +1 -0
  852. package/dist/state/state-validator.js +67 -0
  853. package/dist/state/state-validator.js.map +1 -0
  854. package/dist/translation/epub.d.ts +10 -0
  855. package/dist/translation/epub.d.ts.map +1 -0
  856. package/dist/translation/epub.js +86 -0
  857. package/dist/translation/epub.js.map +1 -0
  858. package/dist/translation/export.d.ts +6 -0
  859. package/dist/translation/export.d.ts.map +1 -0
  860. package/dist/translation/export.js +68 -0
  861. package/dist/translation/export.js.map +1 -0
  862. package/dist/translation/index.d.ts +9 -0
  863. package/dist/translation/index.d.ts.map +1 -0
  864. package/dist/translation/index.js +9 -0
  865. package/dist/translation/index.js.map +1 -0
  866. package/dist/translation/llm-model.d.ts +11 -0
  867. package/dist/translation/llm-model.d.ts.map +1 -0
  868. package/dist/translation/llm-model.js +135 -0
  869. package/dist/translation/llm-model.js.map +1 -0
  870. package/dist/translation/project.d.ts +3 -0
  871. package/dist/translation/project.d.ts.map +1 -0
  872. package/dist/translation/project.js +76 -0
  873. package/dist/translation/project.js.map +1 -0
  874. package/dist/translation/run-store.d.ts +12 -0
  875. package/dist/translation/run-store.d.ts.map +1 -0
  876. package/dist/translation/run-store.js +69 -0
  877. package/dist/translation/run-store.js.map +1 -0
  878. package/dist/translation/runner.d.ts +6 -0
  879. package/dist/translation/runner.d.ts.map +1 -0
  880. package/dist/translation/runner.js +155 -0
  881. package/dist/translation/runner.js.map +1 -0
  882. package/dist/translation/source.d.ts +12 -0
  883. package/dist/translation/source.d.ts.map +1 -0
  884. package/dist/translation/source.js +66 -0
  885. package/dist/translation/source.js.map +1 -0
  886. package/dist/translation/text.d.ts +10 -0
  887. package/dist/translation/text.d.ts.map +1 -0
  888. package/dist/translation/text.js +110 -0
  889. package/dist/translation/text.js.map +1 -0
  890. package/dist/translation/types.d.ts +96 -0
  891. package/dist/translation/types.d.ts.map +1 -0
  892. package/dist/translation/types.js +2 -0
  893. package/dist/translation/types.js.map +1 -0
  894. package/dist/utils/analytics.d.ts +39 -0
  895. package/dist/utils/analytics.d.ts.map +1 -0
  896. package/dist/utils/analytics.js +50 -0
  897. package/dist/utils/analytics.js.map +1 -0
  898. package/dist/utils/atomic-file-set.d.ts +12 -0
  899. package/dist/utils/atomic-file-set.d.ts.map +1 -0
  900. package/dist/utils/atomic-file-set.js +93 -0
  901. package/dist/utils/atomic-file-set.js.map +1 -0
  902. package/dist/utils/book-eval.d.ts +35 -0
  903. package/dist/utils/book-eval.d.ts.map +1 -0
  904. package/dist/utils/book-eval.js +116 -0
  905. package/dist/utils/book-eval.js.map +1 -0
  906. package/dist/utils/book-id.d.ts +4 -0
  907. package/dist/utils/book-id.d.ts.map +1 -0
  908. package/dist/utils/book-id.js +27 -0
  909. package/dist/utils/book-id.js.map +1 -0
  910. package/dist/utils/cadence-policy.d.ts +36 -0
  911. package/dist/utils/cadence-policy.d.ts.map +1 -0
  912. package/dist/utils/cadence-policy.js +38 -0
  913. package/dist/utils/cadence-policy.js.map +1 -0
  914. package/dist/utils/chapter-cadence.d.ts +34 -0
  915. package/dist/utils/chapter-cadence.d.ts.map +1 -0
  916. package/dist/utils/chapter-cadence.js +143 -0
  917. package/dist/utils/chapter-cadence.js.map +1 -0
  918. package/dist/utils/chapter-memo-parser.d.ts +22 -0
  919. package/dist/utils/chapter-memo-parser.d.ts.map +1 -0
  920. package/dist/utils/chapter-memo-parser.js +158 -0
  921. package/dist/utils/chapter-memo-parser.js.map +1 -0
  922. package/dist/utils/chapter-splitter.d.ts +18 -0
  923. package/dist/utils/chapter-splitter.d.ts.map +1 -0
  924. package/dist/utils/chapter-splitter.js +60 -0
  925. package/dist/utils/chapter-splitter.js.map +1 -0
  926. package/dist/utils/config-loader.d.ts +11 -0
  927. package/dist/utils/config-loader.d.ts.map +1 -0
  928. package/dist/utils/config-loader.js +16 -0
  929. package/dist/utils/config-loader.js.map +1 -0
  930. package/dist/utils/context-assembly.d.ts +29 -0
  931. package/dist/utils/context-assembly.d.ts.map +1 -0
  932. package/dist/utils/context-assembly.js +117 -0
  933. package/dist/utils/context-assembly.js.map +1 -0
  934. package/dist/utils/context-filter.d.ts +31 -0
  935. package/dist/utils/context-filter.d.ts.map +1 -0
  936. package/dist/utils/context-filter.js +163 -0
  937. package/dist/utils/context-filter.js.map +1 -0
  938. package/dist/utils/effective-llm-config.d.ts +34 -0
  939. package/dist/utils/effective-llm-config.d.ts.map +1 -0
  940. package/dist/utils/effective-llm-config.js +463 -0
  941. package/dist/utils/effective-llm-config.js.map +1 -0
  942. package/dist/utils/governed-context.d.ts +11 -0
  943. package/dist/utils/governed-context.d.ts.map +1 -0
  944. package/dist/utils/governed-context.js +42 -0
  945. package/dist/utils/governed-context.js.map +1 -0
  946. package/dist/utils/governed-working-set.d.ts +18 -0
  947. package/dist/utils/governed-working-set.d.ts.map +1 -0
  948. package/dist/utils/governed-working-set.js +296 -0
  949. package/dist/utils/governed-working-set.js.map +1 -0
  950. package/dist/utils/hook-arbiter.d.ts +16 -0
  951. package/dist/utils/hook-arbiter.d.ts.map +1 -0
  952. package/dist/utils/hook-arbiter.js +161 -0
  953. package/dist/utils/hook-arbiter.js.map +1 -0
  954. package/dist/utils/hook-governance.d.ts +26 -0
  955. package/dist/utils/hook-governance.d.ts.map +1 -0
  956. package/dist/utils/hook-governance.js +65 -0
  957. package/dist/utils/hook-governance.js.map +1 -0
  958. package/dist/utils/hook-health.d.ts +15 -0
  959. package/dist/utils/hook-health.d.ts.map +1 -0
  960. package/dist/utils/hook-health.js +131 -0
  961. package/dist/utils/hook-health.js.map +1 -0
  962. package/dist/utils/hook-ledger-validator.d.ts +82 -0
  963. package/dist/utils/hook-ledger-validator.d.ts.map +1 -0
  964. package/dist/utils/hook-ledger-validator.js +225 -0
  965. package/dist/utils/hook-ledger-validator.js.map +1 -0
  966. package/dist/utils/hook-lifecycle.d.ts +41 -0
  967. package/dist/utils/hook-lifecycle.d.ts.map +1 -0
  968. package/dist/utils/hook-lifecycle.js +183 -0
  969. package/dist/utils/hook-lifecycle.js.map +1 -0
  970. package/dist/utils/hook-policy.d.ts +57 -0
  971. package/dist/utils/hook-policy.d.ts.map +1 -0
  972. package/dist/utils/hook-policy.js +96 -0
  973. package/dist/utils/hook-policy.js.map +1 -0
  974. package/dist/utils/hook-promotion.d.ts +83 -0
  975. package/dist/utils/hook-promotion.d.ts.map +1 -0
  976. package/dist/utils/hook-promotion.js +241 -0
  977. package/dist/utils/hook-promotion.js.map +1 -0
  978. package/dist/utils/hook-stale-detection.d.ts +51 -0
  979. package/dist/utils/hook-stale-detection.d.ts.map +1 -0
  980. package/dist/utils/hook-stale-detection.js +125 -0
  981. package/dist/utils/hook-stale-detection.js.map +1 -0
  982. package/dist/utils/language.d.ts +19 -0
  983. package/dist/utils/language.d.ts.map +1 -0
  984. package/dist/utils/language.js +29 -0
  985. package/dist/utils/language.js.map +1 -0
  986. package/dist/utils/length-metrics.d.ts +12 -0
  987. package/dist/utils/length-metrics.d.ts.map +1 -0
  988. package/dist/utils/length-metrics.js +85 -0
  989. package/dist/utils/length-metrics.js.map +1 -0
  990. package/dist/utils/llm-endpoint-auth.d.ts +5 -0
  991. package/dist/utils/llm-endpoint-auth.d.ts.map +1 -0
  992. package/dist/utils/llm-endpoint-auth.js +36 -0
  993. package/dist/utils/llm-endpoint-auth.js.map +1 -0
  994. package/dist/utils/llm-env.d.ts +14 -0
  995. package/dist/utils/llm-env.d.ts.map +1 -0
  996. package/dist/utils/llm-env.js +53 -0
  997. package/dist/utils/llm-env.js.map +1 -0
  998. package/dist/utils/logger.d.ts +31 -0
  999. package/dist/utils/logger.d.ts.map +1 -0
  1000. package/dist/utils/logger.js +79 -0
  1001. package/dist/utils/logger.js.map +1 -0
  1002. package/dist/utils/long-span-fatigue.d.ts +28 -0
  1003. package/dist/utils/long-span-fatigue.d.ts.map +1 -0
  1004. package/dist/utils/long-span-fatigue.js +406 -0
  1005. package/dist/utils/long-span-fatigue.js.map +1 -0
  1006. package/dist/utils/memory-retrieval.d.ts +71 -0
  1007. package/dist/utils/memory-retrieval.d.ts.map +1 -0
  1008. package/dist/utils/memory-retrieval.js +360 -0
  1009. package/dist/utils/memory-retrieval.js.map +1 -0
  1010. package/dist/utils/narrative-control.d.ts +16 -0
  1011. package/dist/utils/narrative-control.d.ts.map +1 -0
  1012. package/dist/utils/narrative-control.js +131 -0
  1013. package/dist/utils/narrative-control.js.map +1 -0
  1014. package/dist/utils/outline-paths.d.ts +63 -0
  1015. package/dist/utils/outline-paths.d.ts.map +1 -0
  1016. package/dist/utils/outline-paths.js +311 -0
  1017. package/dist/utils/outline-paths.js.map +1 -0
  1018. package/dist/utils/path-safety.d.ts +2 -0
  1019. package/dist/utils/path-safety.d.ts.map +1 -0
  1020. package/dist/utils/path-safety.js +11 -0
  1021. package/dist/utils/path-safety.js.map +1 -0
  1022. package/dist/utils/planning-materials.d.ts +35 -0
  1023. package/dist/utils/planning-materials.d.ts.map +1 -0
  1024. package/dist/utils/planning-materials.js +123 -0
  1025. package/dist/utils/planning-materials.js.map +1 -0
  1026. package/dist/utils/posix-path.d.ts +2 -0
  1027. package/dist/utils/posix-path.d.ts.map +1 -0
  1028. package/dist/utils/posix-path.js +7 -0
  1029. package/dist/utils/posix-path.js.map +1 -0
  1030. package/dist/utils/pov-filter.d.ts +30 -0
  1031. package/dist/utils/pov-filter.d.ts.map +1 -0
  1032. package/dist/utils/pov-filter.js +129 -0
  1033. package/dist/utils/pov-filter.js.map +1 -0
  1034. package/dist/utils/proxy-fetch.d.ts +9 -0
  1035. package/dist/utils/proxy-fetch.d.ts.map +1 -0
  1036. package/dist/utils/proxy-fetch.js +31 -0
  1037. package/dist/utils/proxy-fetch.js.map +1 -0
  1038. package/dist/utils/runtime-writer.d.ts +14 -0
  1039. package/dist/utils/runtime-writer.d.ts.map +1 -0
  1040. package/dist/utils/runtime-writer.js +21 -0
  1041. package/dist/utils/runtime-writer.js.map +1 -0
  1042. package/dist/utils/spot-fix-patches.d.ts +21 -0
  1043. package/dist/utils/spot-fix-patches.d.ts.map +1 -0
  1044. package/dist/utils/spot-fix-patches.js +148 -0
  1045. package/dist/utils/spot-fix-patches.js.map +1 -0
  1046. package/dist/utils/story-markdown.d.ts +13 -0
  1047. package/dist/utils/story-markdown.d.ts.map +1 -0
  1048. package/dist/utils/story-markdown.js +320 -0
  1049. package/dist/utils/story-markdown.js.map +1 -0
  1050. package/dist/utils/web-search.d.ts +28 -0
  1051. package/dist/utils/web-search.d.ts.map +1 -0
  1052. package/dist/utils/web-search.js +72 -0
  1053. package/dist/utils/web-search.js.map +1 -0
  1054. package/dist/utils/writing-methodology.d.ts +10 -0
  1055. package/dist/utils/writing-methodology.d.ts.map +1 -0
  1056. package/dist/utils/writing-methodology.js +163 -0
  1057. package/dist/utils/writing-methodology.js.map +1 -0
  1058. package/genres/cozy.md +43 -0
  1059. package/genres/cultivation.md +42 -0
  1060. package/genres/dungeon-core.md +40 -0
  1061. package/genres/horror.md +51 -0
  1062. package/genres/isekai.md +43 -0
  1063. package/genres/litrpg.md +43 -0
  1064. package/genres/other.md +24 -0
  1065. package/genres/progression.md +41 -0
  1066. package/genres/romantasy.md +45 -0
  1067. package/genres/sci-fi.md +42 -0
  1068. package/genres/system-apocalypse.md +40 -0
  1069. package/genres/tower-climber.md +41 -0
  1070. package/genres/urban.md +53 -0
  1071. package/genres/xianxia.md +46 -0
  1072. package/genres/xuanhuan.md +64 -0
  1073. package/package.json +76 -0
  1074. package/skills/inkos-interactive-film/SKILL.md +16 -0
  1075. package/skills/inkos-interactive-film/references/branch-state-craft.md +13 -0
  1076. package/skills/inkos-long-market-research/SKILL.md +16 -0
  1077. package/skills/inkos-long-market-research/references/research-rubric.md +19 -0
  1078. package/skills/inkos-long-story-analysis/SKILL.md +17 -0
  1079. package/skills/inkos-long-story-analysis/references/analysis-lens.md +16 -0
  1080. package/skills/inkos-long-writing/SKILL.md +17 -0
  1081. package/skills/inkos-long-writing/references/reader-contract-and-progression.md +13 -0
  1082. package/skills/inkos-long-writing/references/scene-dialogue-and-length-budget.md +13 -0
  1083. package/skills/inkos-play-world/SKILL.md +17 -0
  1084. package/skills/inkos-play-world/references/world-turn-continuity.md +13 -0
  1085. package/skills/inkos-script-writing/SKILL.md +15 -0
  1086. package/skills/inkos-script-writing/references/episode-scene-craft.md +13 -0
  1087. package/skills/inkos-short-market-research/SKILL.md +17 -0
  1088. package/skills/inkos-short-market-research/references/short-market-rubric.md +16 -0
  1089. package/skills/inkos-short-story-analysis/SKILL.md +16 -0
  1090. package/skills/inkos-short-story-analysis/references/deconstruction-template.md +14 -0
  1091. package/skills/inkos-short-writing/SKILL.md +18 -0
  1092. package/skills/inkos-short-writing/references/production-checklist.md +19 -0
  1093. package/skills/inkos-story-cover/SKILL.md +17 -0
  1094. package/skills/inkos-story-cover/references/cover-brief.md +16 -0
  1095. package/skills/inkos-story-deslop/SKILL.md +16 -0
  1096. package/skills/inkos-story-deslop/references/semantic-cleanup.md +13 -0
  1097. package/skills/inkos-story-import/SKILL.md +19 -0
  1098. package/skills/inkos-story-import/references/reconstruction-rubric.md +13 -0
  1099. package/skills/inkos-story-review/SKILL.md +16 -0
  1100. package/skills/inkos-story-review/references/review-matrix.md +21 -0
  1101. package/skills/inkos-storyboard/SKILL.md +15 -0
  1102. package/skills/inkos-storyboard/references/shot-continuity.md +13 -0
  1103. package/skills/inkos-translation/SKILL.md +14 -0
  1104. package/skills/inkos-translation/references/long-form-consistency.md +13 -0
@@ -0,0 +1,2974 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { createLLMClient } from "../llm/provider.js";
3
+ import { runWorkerAgent } from "../agent/worker-agent.js";
4
+ import { ArchitectAgent } from "../agents/architect.js";
5
+ import { FoundationReviewerAgent, FoundationReviewParseError, } from "../agents/foundation-reviewer.js";
6
+ import { PlannerAgent } from "../agents/planner.js";
7
+ import { ComposerAgent, composeGovernedChapter, contextBudgetFromClient } from "../agents/composer.js";
8
+ import { WriterAgent } from "../agents/writer.js";
9
+ import { ChapterAnalyzerAgent } from "../agents/chapter-analyzer.js";
10
+ import { ContinuityAuditor } from "../agents/continuity.js";
11
+ import { ReviserAgent, DEFAULT_REVISE_MODE } from "../agents/reviser.js";
12
+ import { StateValidatorAgent } from "../agents/state-validator.js";
13
+ import { RadarAgent } from "../agents/radar.js";
14
+ import { readGenreProfile } from "../agents/rules-reader.js";
15
+ import { analyzeAITells } from "../agents/ai-tells.js";
16
+ import { analyzeSensitiveWords } from "../agents/sensitive-words.js";
17
+ import { StateManager } from "../state/manager.js";
18
+ import { archiveChapterVersion, readChapterUserBrief } from "../state/chapter-workspace.js";
19
+ import { MemoryDB } from "../state/memory-db.js";
20
+ import { dispatchNotification, dispatchWebhookEvent } from "../notify/dispatcher.js";
21
+ import { appendActivatedSkillGuidance } from "../agents/base.js";
22
+ import { HooksStateSchema } from "../models/runtime-state.js";
23
+ import { buildLengthSpec, countChapterLength, formatLengthCount, isOutsideHardRange, resolveLengthCountingMode } from "../utils/length-metrics.js";
24
+ import { analyzeLongSpanFatigue } from "../utils/long-span-fatigue.js";
25
+ import { buildWritingMethodologySection } from "../utils/writing-methodology.js";
26
+ import { isNewLayoutBook, readCharacterContext, readStoryFrame, readVolumeMap, } from "../utils/outline-paths.js";
27
+ import { loadNarrativeMemorySeed, loadSnapshotCurrentStateFacts } from "../state/runtime-state-store.js";
28
+ import { rewriteStructuredStateFromMarkdown } from "../state/state-bootstrap.js";
29
+ import { readFile, readdir, writeFile, mkdir, rename, rm, stat } from "node:fs/promises";
30
+ import { join } from "node:path";
31
+ import { parseStateDegradedReviewNote, resolveStateDegradedBaseStatus, retrySettlementAfterValidationFailure, } from "./chapter-state-recovery.js";
32
+ import { persistChapterArtifacts } from "./chapter-persistence.js";
33
+ import { runChapterReviewCycle } from "./chapter-review-cycle.js";
34
+ import { validateChapterTruthPersistence } from "./chapter-truth-validation.js";
35
+ import { loadPersistedPlan, relativeToBookDir, savePersistedPlan } from "./persisted-governed-plan.js";
36
+ import { selectBookReferenceContext } from "../references/reference-context.js";
37
+ import { commitAtomicFileSet } from "../utils/atomic-file-set.js";
38
+ import { toPosixPath } from "../utils/posix-path.js";
39
+ import { createProductionRunSnapshot, createRangeObservation, writeProductionRunSnapshot, } from "../production/harness.js";
40
+ const SEQUENCE_LEVEL_CATEGORIES = new Set([
41
+ "Pacing Monotony", "节奏单调",
42
+ "Mood Monotony", "情绪单调",
43
+ "Title Collapse", "标题重复",
44
+ "Title Clustering", "标题聚集",
45
+ "Opening Pattern Repetition", "开头同构",
46
+ "Ending Pattern Repetition", "结尾同构",
47
+ ]);
48
+ function isSequenceLevelCategory(category) {
49
+ return SEQUENCE_LEVEL_CATEGORIES.has(category);
50
+ }
51
+ function mergeChapterRevisionInstructions(persistedBrief, currentInstruction) {
52
+ const persisted = persistedBrief.trim();
53
+ const current = currentInstruction?.trim() ?? "";
54
+ if (!persisted)
55
+ return current || undefined;
56
+ if (!current || current === persisted)
57
+ return persisted;
58
+ return [
59
+ "## Persisted chapter brief",
60
+ persisted,
61
+ "",
62
+ "## Current revision instruction",
63
+ current,
64
+ ].join("\n");
65
+ }
66
+ const DEFAULT_IMPORT_FOUNDATION_MAX_FULL_TEXT_CHARS = 80_000;
67
+ const DEFAULT_IMPORT_EDGE_CHAPTER_COUNT = 4;
68
+ const DEFAULT_IMPORT_MIDDLE_ANCHOR_COUNT = 8;
69
+ function formatImportedChapter(chapter, index, language, content = chapter.content) {
70
+ return language === "en"
71
+ ? `Chapter ${index + 1}: ${chapter.title}\n\n${content}`
72
+ : `第${index + 1}章 ${chapter.title}\n\n${content}`;
73
+ }
74
+ function estimateImportFullTextLength(chapters) {
75
+ return chapters.reduce((total, chapter) => total + chapter.title.length + chapter.content.length + 24, 0);
76
+ }
77
+ function pickImportAnchorIndexes(chapterCount, edgeChapterCount, middleAnchorCount) {
78
+ const selected = new Set();
79
+ for (let i = 0; i < Math.min(edgeChapterCount, chapterCount); i++)
80
+ selected.add(i);
81
+ for (let i = Math.max(0, chapterCount - edgeChapterCount); i < chapterCount; i++)
82
+ selected.add(i);
83
+ const middleStart = Math.min(edgeChapterCount, chapterCount);
84
+ const middleEnd = Math.max(middleStart, chapterCount - edgeChapterCount);
85
+ const middleSize = middleEnd - middleStart;
86
+ const anchors = Math.min(middleAnchorCount, middleSize);
87
+ for (let i = 0; i < anchors; i++) {
88
+ const offset = Math.floor(((i + 1) * middleSize) / (anchors + 1));
89
+ selected.add(Math.min(chapterCount - 1, middleStart + offset));
90
+ }
91
+ return [...selected].sort((a, b) => a - b);
92
+ }
93
+ function buildTitleCatalog(chapters, language) {
94
+ return chapters.map((chapter, index) => language === "en"
95
+ ? `- Chapter ${index + 1}: ${chapter.title} (${chapter.content.length} chars)`
96
+ : `- 第${index + 1}章:${chapter.title}(${chapter.content.length}字)`).join("\n");
97
+ }
98
+ /**
99
+ * Build the architect external-context for a side-story (番外) foundation: frame
100
+ * it as a companion work that reuses the parent canon's cast/world but tells an
101
+ * independent side plot, and attach the parent canon as reference material.
102
+ */
103
+ export function buildSpinoffFoundationContext(parentCanon, direction, language) {
104
+ const dir = direction?.trim();
105
+ if (language === "en") {
106
+ return [
107
+ "## This is a SIDE-STORY (番外)",
108
+ "Reuse the established characters, world, and rules from the parent canon below. Tell an INDEPENDENT side plot — a bonus arc, a character backstory, or a what-if — that does NOT advance or contradict the parent work's main storyline.",
109
+ dir ? `\n## Side-story direction\n${dir}` : "",
110
+ `\n## Parent canon (reuse these characters and settings)\n${parentCanon}`,
111
+ ].filter(Boolean).join("\n");
112
+ }
113
+ return [
114
+ "## 这是一部番外",
115
+ "复用下方正传正典里已确立的角色、世界观与规则。讲一个独立的侧篇故事——支线、角色前传或 what-if——不要推进或违背正传的主线剧情。",
116
+ dir ? `\n## 番外方向\n${dir}` : "",
117
+ `\n## 正传正典(复用以下角色与设定)\n${parentCanon}`,
118
+ ].filter(Boolean).join("\n");
119
+ }
120
+ export function buildImportFoundationSource(chapters, language, options = {}) {
121
+ const maxFullTextChars = options.maxFullTextChars ?? DEFAULT_IMPORT_FOUNDATION_MAX_FULL_TEXT_CHARS;
122
+ const edgeChapterCount = options.edgeChapterCount ?? DEFAULT_IMPORT_EDGE_CHAPTER_COUNT;
123
+ const middleAnchorCount = options.middleAnchorCount ?? DEFAULT_IMPORT_MIDDLE_ANCHOR_COUNT;
124
+ if (estimateImportFullTextLength(chapters) <= maxFullTextChars) {
125
+ return chapters.map((chapter, index) => formatImportedChapter(chapter, index, language)).join("\n\n---\n\n");
126
+ }
127
+ const anchorIndexes = pickImportAnchorIndexes(chapters.length, edgeChapterCount, middleAnchorCount);
128
+ const header = language === "en"
129
+ ? [
130
+ "## Import foundation source package",
131
+ "",
132
+ `The imported book has ${chapters.length} chapters. This package selects complete opening chapters, the ending/continuation point, and complete middle anchors. It also keeps the full title catalog. Unselected chapters will be replayed sequentially after foundation generation to rebuild truth files.`,
133
+ ].join("\n")
134
+ : [
135
+ "## 导入基础设定压缩资料包",
136
+ "",
137
+ `本次导入共 ${chapters.length} 章。这里选取完整的开篇章节、结尾续写点和中段锚点,并保留完整标题目录;未选章节将在后续顺序回放中逐章分析并沉淀 truth files。`,
138
+ ].join("\n");
139
+ const catalogTitle = language === "en" ? "## Complete chapter title catalog" : "## 完整章节标题目录";
140
+ const anchorsTitle = language === "en" ? "## Complete source chapters selected for architecture" : "## 用于反推基础设定的完整锚点章节";
141
+ const anchorText = anchorIndexes
142
+ .map((index) => {
143
+ const chapter = chapters[index];
144
+ return formatImportedChapter(chapter, index, language);
145
+ })
146
+ .join("\n\n---\n\n");
147
+ return [
148
+ header,
149
+ "",
150
+ catalogTitle,
151
+ buildTitleCatalog(chapters, language),
152
+ "",
153
+ anchorsTitle,
154
+ anchorText,
155
+ ].join("\n");
156
+ }
157
+ /** Human-readable description of each manual-revision gate, surfaced in revisionDiagnostics. */
158
+ const REVISION_GATE_STANDARDS = {
159
+ strict: "A revision is applied only when blocking, critical, and AI-tell counts do not worsen, and at least blocking or AI-tell issues improve.",
160
+ lenient: "A revision is applied whenever blocking, critical, and AI-tell counts do not worsen; no improvement is required (lenient gate).",
161
+ always: "Manual revisions are always applied; audit counts are recorded for reference only (always gate).",
162
+ };
163
+ export class PipelineRunner {
164
+ state;
165
+ config;
166
+ agentClients = new Map();
167
+ operationContext = new AsyncLocalStorage();
168
+ constructor(config) {
169
+ this.config = config;
170
+ this.state = new StateManager(config.projectRoot);
171
+ }
172
+ async runWithAbortSignal(signal, task) {
173
+ return this.runWithAgentContext({ signal }, task);
174
+ }
175
+ async runWithAgentContext(context, task) {
176
+ const current = this.operationContext.getStore();
177
+ const merged = {
178
+ signal: context.signal ?? current?.signal,
179
+ activatedSkills: context.activatedSkills ?? current?.activatedSkills,
180
+ };
181
+ merged.signal?.throwIfAborted();
182
+ return this.operationContext.run(merged, async () => {
183
+ merged.signal?.throwIfAborted();
184
+ return task();
185
+ });
186
+ }
187
+ currentAbortSignal() {
188
+ return this.operationContext.getStore()?.signal;
189
+ }
190
+ currentActivatedSkills() {
191
+ return this.operationContext.getStore()?.activatedSkills;
192
+ }
193
+ throwIfOperationAborted() {
194
+ this.currentAbortSignal()?.throwIfAborted();
195
+ }
196
+ localize(language, messages) {
197
+ return language === "en" ? messages.en : messages.zh;
198
+ }
199
+ async resolveBookLanguage(book) {
200
+ if (book.language) {
201
+ return book.language;
202
+ }
203
+ try {
204
+ const { profile } = await this.loadGenreProfile(book.genre);
205
+ return profile.language;
206
+ }
207
+ catch {
208
+ return "zh";
209
+ }
210
+ }
211
+ async resolveBookLanguageById(bookId) {
212
+ try {
213
+ const book = await this.state.loadBookConfig(bookId);
214
+ return await this.resolveBookLanguage(book);
215
+ }
216
+ catch {
217
+ return "zh";
218
+ }
219
+ }
220
+ languageFromLengthSpec(lengthSpec) {
221
+ return lengthSpec.countingMode === "en_words" ? "en" : "zh";
222
+ }
223
+ logStage(language, message) {
224
+ this.config.logger?.info(`${this.localize(language, { zh: "阶段:", en: "Stage: " })}${this.localize(language, message)}`);
225
+ }
226
+ logInfo(language, message) {
227
+ this.config.logger?.info(this.localize(language, message));
228
+ }
229
+ logWarn(language, message) {
230
+ this.config.logger?.warn(this.localize(language, message));
231
+ }
232
+ async tryGenerateStyleGuide(bookId, referenceText, sourceName, language) {
233
+ try {
234
+ await this.generateStyleGuide(bookId, referenceText, sourceName);
235
+ }
236
+ catch (error) {
237
+ const resolvedLanguage = language ?? await this.resolveBookLanguageById(bookId);
238
+ const detail = error instanceof Error ? error.message : String(error);
239
+ this.logWarn(resolvedLanguage, {
240
+ zh: `风格指纹提取失败,已跳过:${detail}`,
241
+ en: `Style fingerprint extraction failed and was skipped: ${detail}`,
242
+ });
243
+ }
244
+ }
245
+ async generateAndReviewFoundation(params) {
246
+ const maxRetries = params.maxRetries ?? this.config.foundationReviewRetries ?? 2;
247
+ let foundation = await params.generate();
248
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
249
+ this.logStage(params.stageLanguage, {
250
+ zh: `审核基础设定(第${attempt + 1}轮)`,
251
+ en: `reviewing foundation (round ${attempt + 1})`,
252
+ });
253
+ let review;
254
+ try {
255
+ review = await params.reviewer.review({
256
+ foundation,
257
+ mode: params.mode,
258
+ sourceCanon: params.sourceCanon,
259
+ styleGuide: params.styleGuide,
260
+ language: params.language,
261
+ targetChapters: params.targetChapters,
262
+ });
263
+ }
264
+ catch (error) {
265
+ if (!(error instanceof FoundationReviewParseError))
266
+ throw error;
267
+ this.logWarn(params.stageLanguage, {
268
+ zh: `基础设定审核输出无法解析,已保留当前版本且不会自动重生成:${error.message}`,
269
+ en: `Foundation review output could not be parsed; keeping the current version without automatic regeneration: ${error.message}`,
270
+ });
271
+ return foundation;
272
+ }
273
+ this.config.logger?.info(`Foundation review: ${review.totalScore}/100 ${review.passed ? "PASSED" : "REJECTED"}`);
274
+ for (const dim of review.dimensions) {
275
+ this.config.logger?.info(` [${dim.score}] ${dim.name.slice(0, 40)}`);
276
+ }
277
+ if (review.passed) {
278
+ return foundation;
279
+ }
280
+ this.logWarn(params.stageLanguage, {
281
+ zh: `基础设定未通过审核(${review.totalScore}分),正在重新生成...`,
282
+ en: `Foundation rejected (${review.totalScore}/100), regenerating...`,
283
+ });
284
+ foundation = await params.generate(this.buildFoundationReviewFeedback(review, params.language));
285
+ }
286
+ // Final review
287
+ let finalReview;
288
+ try {
289
+ finalReview = await params.reviewer.review({
290
+ foundation,
291
+ mode: params.mode,
292
+ sourceCanon: params.sourceCanon,
293
+ styleGuide: params.styleGuide,
294
+ language: params.language,
295
+ targetChapters: params.targetChapters,
296
+ });
297
+ }
298
+ catch (error) {
299
+ if (!(error instanceof FoundationReviewParseError))
300
+ throw error;
301
+ this.logWarn(params.stageLanguage, {
302
+ zh: `基础设定最终审核输出无法解析,已保留当前版本:${error.message}`,
303
+ en: `Final foundation review output could not be parsed; keeping the current version: ${error.message}`,
304
+ });
305
+ return foundation;
306
+ }
307
+ this.config.logger?.info(`Foundation final review: ${finalReview.totalScore}/100 ${finalReview.passed ? "PASSED" : "ACCEPTED (max retries)"}`);
308
+ return foundation;
309
+ }
310
+ buildFoundationReviewFeedback(review, language) {
311
+ const dimensionLines = review.dimensions
312
+ .map((dimension) => (language === "en"
313
+ ? `- ${dimension.name} [${dimension.score}]: ${dimension.feedback}`
314
+ : `- ${dimension.name}(${dimension.score}分):${dimension.feedback}`))
315
+ .join("\n");
316
+ return language === "en"
317
+ ? [
318
+ "## Overall Feedback",
319
+ review.overallFeedback,
320
+ "",
321
+ "## Dimension Notes",
322
+ dimensionLines || "- none",
323
+ ].join("\n")
324
+ : [
325
+ "## 总评",
326
+ review.overallFeedback,
327
+ "",
328
+ "## 分项问题",
329
+ dimensionLines || "- 无",
330
+ ].join("\n");
331
+ }
332
+ agentCtx(bookId) {
333
+ return {
334
+ client: this.config.client,
335
+ model: this.config.model,
336
+ projectRoot: this.config.projectRoot,
337
+ bookId,
338
+ logger: this.config.logger,
339
+ onStreamProgress: this.config.onStreamProgress,
340
+ };
341
+ }
342
+ resolveOverride(agentName) {
343
+ const override = this.config.modelOverrides?.[agentName];
344
+ if (!override) {
345
+ return { model: this.config.model, client: this.config.client };
346
+ }
347
+ if (typeof override === "string") {
348
+ return { model: override, client: this.config.client };
349
+ }
350
+ // Full override — needs its own client if baseUrl differs
351
+ if (!override.baseUrl) {
352
+ return { model: override.model, client: this.config.client };
353
+ }
354
+ const base = this.config.defaultLLMConfig;
355
+ const provider = override.provider ?? base?.provider ?? "custom";
356
+ const apiKeySource = override.apiKeyEnv
357
+ ? `env:${override.apiKeyEnv}`
358
+ : `base:${base?.apiKey ?? ""}`;
359
+ const stream = override.stream ?? base?.stream ?? true;
360
+ const apiFormat = base?.apiFormat ?? "chat";
361
+ const cacheKey = [
362
+ provider,
363
+ override.baseUrl,
364
+ apiKeySource,
365
+ `stream:${stream}`,
366
+ `format:${apiFormat}`,
367
+ ].join("|");
368
+ let client = this.agentClients.get(cacheKey);
369
+ if (!client) {
370
+ const apiKey = override.apiKeyEnv
371
+ ? process.env[override.apiKeyEnv] ?? ""
372
+ : base?.apiKey ?? "";
373
+ client = createLLMClient({
374
+ provider,
375
+ service: base?.service ?? "custom",
376
+ configSource: base?.configSource ?? "env",
377
+ baseUrl: override.baseUrl,
378
+ apiKey,
379
+ model: override.model,
380
+ temperature: base?.temperature ?? 0.7,
381
+ thinkingBudget: base?.thinkingBudget ?? 0,
382
+ apiFormat,
383
+ stream,
384
+ });
385
+ this.agentClients.set(cacheKey, client);
386
+ }
387
+ return { model: override.model, client };
388
+ }
389
+ agentCtxFor(agent, bookId) {
390
+ const { model, client } = this.resolveOverride(agent);
391
+ return {
392
+ client,
393
+ model,
394
+ projectRoot: this.config.projectRoot,
395
+ bookId,
396
+ logger: this.config.logger?.child(agent),
397
+ onStreamProgress: this.config.onStreamProgress,
398
+ signal: this.currentAbortSignal(),
399
+ activatedSkills: this.currentActivatedSkills(),
400
+ };
401
+ }
402
+ createAgentContext(agent, bookId) {
403
+ return this.agentCtxFor(agent, bookId);
404
+ }
405
+ async pathExists(path) {
406
+ try {
407
+ await stat(path);
408
+ return true;
409
+ }
410
+ catch {
411
+ return false;
412
+ }
413
+ }
414
+ async loadGenreProfile(genre) {
415
+ const parsed = await readGenreProfile(this.config.projectRoot, genre);
416
+ return { profile: parsed.profile };
417
+ }
418
+ // ---------------------------------------------------------------------------
419
+ // Atomic operations (composable by OpenClaw or agent mode)
420
+ // ---------------------------------------------------------------------------
421
+ async runRadar() {
422
+ const radar = new RadarAgent(this.agentCtxFor("radar"), this.config.radarSources);
423
+ return radar.scan();
424
+ }
425
+ async initBook(book, options = {}) {
426
+ const architect = new ArchitectAgent(this.agentCtxFor("architect", book.id));
427
+ const bookDir = this.state.bookDir(book.id);
428
+ const stagingBookDir = join(this.state.booksDir, `.tmp-book-create-${book.id}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`);
429
+ const stageLanguage = await this.resolveBookLanguage(book);
430
+ const effectiveExternalContext = options.externalContext ?? this.config.externalContext;
431
+ this.logStage(stageLanguage, { zh: "生成基础设定", en: "generating foundation" });
432
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
433
+ const reviewer = new FoundationReviewerAgent(this.agentCtxFor("foundation-reviewer", book.id));
434
+ const resolvedLanguage = (book.language ?? gp.language) === "en" ? "en" : "zh";
435
+ const foundation = await this.generateAndReviewFoundation({
436
+ generate: (reviewFeedback) => architect.generateFoundation(book, effectiveExternalContext, reviewFeedback),
437
+ reviewer,
438
+ mode: "original",
439
+ language: resolvedLanguage,
440
+ stageLanguage,
441
+ targetChapters: book.targetChapters,
442
+ });
443
+ try {
444
+ this.logStage(stageLanguage, { zh: "保存书籍配置", en: "saving book config" });
445
+ await this.state.saveBookConfigAt(stagingBookDir, book);
446
+ this.logStage(stageLanguage, { zh: "写入基础设定文件", en: "writing foundation files" });
447
+ await architect.writeFoundationFiles(stagingBookDir, foundation, gp.numericalSystem, book.language ?? gp.language);
448
+ if (effectiveExternalContext && effectiveExternalContext.trim().length > 0) {
449
+ const storyDir = join(stagingBookDir, "story");
450
+ await mkdir(storyDir, { recursive: true });
451
+ await writeFile(join(storyDir, "brief.md"), effectiveExternalContext, "utf-8");
452
+ }
453
+ this.logStage(stageLanguage, { zh: "初始化控制文档", en: "initializing control documents" });
454
+ await this.state.ensureControlDocumentsAt(stagingBookDir, book.language ?? gp.language, options.authorIntent ?? effectiveExternalContext);
455
+ if (options.currentFocus?.trim()) {
456
+ await writeFile(join(stagingBookDir, "story", "current_focus.md"), options.currentFocus.trimEnd() + "\n", "utf-8");
457
+ }
458
+ await this.state.saveChapterIndexAt(stagingBookDir, []);
459
+ this.logStage(stageLanguage, { zh: "创建初始快照", en: "creating initial snapshot" });
460
+ await this.state.snapshotStateAt(stagingBookDir, 0);
461
+ if (await this.pathExists(bookDir)) {
462
+ if (await this.state.isCompleteBookDirectory(bookDir)) {
463
+ throw new Error(`Book "${book.id}" already exists at books/${book.id}/. Use a different title or delete the existing book first.`);
464
+ }
465
+ await rm(bookDir, { recursive: true, force: true });
466
+ }
467
+ await rename(stagingBookDir, bookDir);
468
+ }
469
+ catch (error) {
470
+ await rm(stagingBookDir, { recursive: true, force: true }).catch(() => undefined);
471
+ throw error;
472
+ }
473
+ }
474
+ /**
475
+ * Revise an existing book foundation without touching runtime chapter state.
476
+ *
477
+ * Legacy books read the flat foundation files as source. Phase 5+ books read
478
+ * the authoritative outline/ and roles/ files instead of the compatibility
479
+ * shims, otherwise large role/story details can be lost during rewrite.
480
+ */
481
+ async reviseFoundation(bookId, feedback) {
482
+ const bookDir = this.state.bookDir(bookId);
483
+ const storyDir = join(bookDir, "story");
484
+ const isPhase5 = await isNewLayoutBook(bookDir);
485
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
486
+ const backupTag = isPhase5 ? "phase5" : "phase4";
487
+ const backupDir = join(storyDir, `.backup-${backupTag}-${timestamp}`);
488
+ await mkdir(backupDir, { recursive: true });
489
+ const flatFiles = ["story_bible.md", "volume_outline.md", "book_rules.md", "character_matrix.md"];
490
+ for (const fileName of flatFiles) {
491
+ try {
492
+ const content = await readFile(join(storyDir, fileName), "utf-8");
493
+ await writeFile(join(backupDir, fileName), content, "utf-8");
494
+ }
495
+ catch {
496
+ // Missing legacy shim files are fine for partially migrated books.
497
+ }
498
+ }
499
+ if (isPhase5) {
500
+ await this.copyDirShallow(join(storyDir, "outline"), join(backupDir, "outline"));
501
+ await this.copyDirRecursive(join(storyDir, "roles"), join(backupDir, "roles"));
502
+ }
503
+ const book = await this.state.loadBookConfig(bookId);
504
+ let oldStoryBible;
505
+ let oldVolumeOutline;
506
+ let oldBookRules;
507
+ let oldCharacterMatrix;
508
+ if (isPhase5) {
509
+ [oldStoryBible, oldVolumeOutline, oldCharacterMatrix] = await Promise.all([
510
+ readStoryFrame(bookDir),
511
+ readVolumeMap(bookDir),
512
+ readCharacterContext(bookDir),
513
+ ]);
514
+ oldBookRules = await readFile(join(storyDir, "book_rules.md"), "utf-8").catch(() => "");
515
+ }
516
+ else {
517
+ [oldStoryBible, oldVolumeOutline, oldBookRules, oldCharacterMatrix] = await Promise.all([
518
+ readFile(join(storyDir, "story_bible.md"), "utf-8").catch(() => ""),
519
+ readFile(join(storyDir, "volume_outline.md"), "utf-8").catch(() => ""),
520
+ readFile(join(storyDir, "book_rules.md"), "utf-8").catch(() => ""),
521
+ readFile(join(storyDir, "character_matrix.md"), "utf-8").catch(() => ""),
522
+ ]);
523
+ }
524
+ const architect = new ArchitectAgent(this.agentCtxFor("architect", bookId));
525
+ const foundation = await architect.generateFoundation(book, undefined, undefined, {
526
+ reviseFrom: {
527
+ storyBible: oldStoryBible,
528
+ volumeOutline: oldVolumeOutline,
529
+ bookRules: oldBookRules,
530
+ characterMatrix: oldCharacterMatrix,
531
+ userFeedback: feedback,
532
+ },
533
+ });
534
+ const reviewer = new FoundationReviewerAgent(this.agentCtxFor("foundation-reviewer", bookId));
535
+ const resolvedLanguage = (book.language ?? "zh") === "en" ? "en" : "zh";
536
+ try {
537
+ const review = await reviewer.review({
538
+ foundation,
539
+ mode: "original",
540
+ language: resolvedLanguage,
541
+ targetChapters: book.targetChapters,
542
+ });
543
+ if (!review.passed) {
544
+ this.config.logger?.warn?.(`[reviseFoundation] Foundation review did not pass; accepting rewrite. Feedback: ${review.overallFeedback ?? ""}`);
545
+ }
546
+ }
547
+ catch (error) {
548
+ this.config.logger?.warn?.(`[reviseFoundation] Foundation review failed and was skipped: ${error instanceof Error ? error.message : String(error)}`);
549
+ }
550
+ const outlineDir = join(storyDir, "outline");
551
+ await mkdir(outlineDir, { recursive: true });
552
+ await mkdir(join(storyDir, "roles", "主要角色"), { recursive: true });
553
+ await mkdir(join(storyDir, "roles", "次要角色"), { recursive: true });
554
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
555
+ await architect.writeFoundationFiles(bookDir, foundation, gp.numericalSystem, book.language ?? gp.language, "revise");
556
+ }
557
+ async copyDirShallow(src, dest) {
558
+ try {
559
+ await mkdir(dest, { recursive: true });
560
+ const entries = await readdir(src);
561
+ await Promise.all(entries.map(async (entry) => {
562
+ try {
563
+ const content = await readFile(join(src, entry), "utf-8");
564
+ await writeFile(join(dest, entry), content, "utf-8");
565
+ }
566
+ catch {
567
+ // Skip unreadable files.
568
+ }
569
+ }));
570
+ }
571
+ catch {
572
+ // Source directory does not exist.
573
+ }
574
+ }
575
+ async copyDirRecursive(src, dest) {
576
+ try {
577
+ await mkdir(dest, { recursive: true });
578
+ const entries = await readdir(src, { withFileTypes: true });
579
+ for (const entry of entries) {
580
+ const srcPath = join(src, entry.name);
581
+ const destPath = join(dest, entry.name);
582
+ if (entry.isDirectory()) {
583
+ await this.copyDirRecursive(srcPath, destPath);
584
+ }
585
+ else if (entry.isFile()) {
586
+ try {
587
+ const content = await readFile(srcPath, "utf-8");
588
+ await writeFile(destPath, content, "utf-8");
589
+ }
590
+ catch {
591
+ // Skip unreadable files.
592
+ }
593
+ }
594
+ }
595
+ }
596
+ catch {
597
+ // Source directory does not exist.
598
+ }
599
+ }
600
+ /** Import external source material and generate fanfic_canon.md */
601
+ async importFanficCanon(bookId, sourceText, sourceName, fanficMode) {
602
+ const { FanficCanonImporter } = await import("../agents/fanfic-canon-importer.js");
603
+ const importer = new FanficCanonImporter(this.agentCtxFor("fanfic-canon-importer", bookId));
604
+ const result = await importer.importFromText(sourceText, sourceName, fanficMode);
605
+ const bookDir = this.state.bookDir(bookId);
606
+ const storyDir = join(bookDir, "story");
607
+ await mkdir(storyDir, { recursive: true });
608
+ await writeFile(join(storyDir, "fanfic_canon.md"), result.fullDocument, "utf-8");
609
+ return result.fullDocument;
610
+ }
611
+ /** One-step fanfic book creation: create book + import canon + generate foundation */
612
+ async initFanficBook(book, sourceText, sourceName, fanficMode) {
613
+ const bookDir = this.state.bookDir(book.id);
614
+ const stageLanguage = await this.resolveBookLanguage(book);
615
+ this.logStage(stageLanguage, { zh: "保存书籍配置", en: "saving book config" });
616
+ await this.state.saveBookConfig(book.id, book);
617
+ // Step 1: Import source material → fanfic_canon.md
618
+ this.logStage(stageLanguage, { zh: "导入同人正典", en: "importing fanfic canon" });
619
+ const fanficCanon = await this.importFanficCanon(book.id, sourceText, sourceName, fanficMode);
620
+ // Step 2: Generate foundation with review loop
621
+ const architect = new ArchitectAgent(this.agentCtxFor("architect", book.id));
622
+ const reviewer = new FoundationReviewerAgent(this.agentCtxFor("foundation-reviewer", book.id));
623
+ this.logStage(stageLanguage, { zh: "生成同人基础设定", en: "generating fanfic foundation" });
624
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
625
+ const resolvedLanguage = (book.language ?? gp.language) === "en" ? "en" : "zh";
626
+ const foundation = await this.generateAndReviewFoundation({
627
+ generate: (reviewFeedback) => architect.generateFanficFoundation(book, fanficCanon, fanficMode, reviewFeedback),
628
+ reviewer,
629
+ mode: "fanfic",
630
+ sourceCanon: fanficCanon,
631
+ language: resolvedLanguage,
632
+ stageLanguage,
633
+ targetChapters: book.targetChapters,
634
+ });
635
+ this.logStage(stageLanguage, { zh: "写入基础设定文件", en: "writing foundation files" });
636
+ await architect.writeFoundationFiles(bookDir, foundation, gp.numericalSystem, book.language ?? gp.language);
637
+ this.logStage(stageLanguage, { zh: "初始化控制文档", en: "initializing control documents" });
638
+ await this.state.ensureControlDocuments(book.id, this.config.externalContext);
639
+ // Step 3: Generate style guide from source material
640
+ if (sourceText.length >= 500) {
641
+ this.logStage(stageLanguage, { zh: "提取原作风格指纹", en: "extracting source style fingerprint" });
642
+ await this.tryGenerateStyleGuide(book.id, sourceText, sourceName, stageLanguage);
643
+ }
644
+ // Step 4: Initialize chapters directory + snapshot
645
+ this.logStage(stageLanguage, { zh: "创建初始快照", en: "creating initial snapshot" });
646
+ await mkdir(join(bookDir, "chapters"), { recursive: true });
647
+ await this.state.saveChapterIndex(book.id, []);
648
+ await this.state.snapshotState(book.id, 0);
649
+ }
650
+ /**
651
+ * Create a side-story (番外) book: a standalone companion that inherits a
652
+ * parent book's world/characters via parent_canon.md, but tells an INDEPENDENT
653
+ * side plot that does not advance or contradict the parent's main-line state.
654
+ * Reuses importCanon (which already builds the parent-canon reference for
655
+ * side-story writing) + the standard original-foundation architect path.
656
+ */
657
+ async initSpinoffBook(book, parentBookId, direction) {
658
+ const bookDir = this.state.bookDir(book.id);
659
+ const stageLanguage = await this.resolveBookLanguage(book);
660
+ this.logStage(stageLanguage, { zh: "保存书籍配置", en: "saving book config" });
661
+ await this.state.saveBookConfig(book.id, book);
662
+ this.logStage(stageLanguage, { zh: "导入正传正典参照", en: "importing parent canon" });
663
+ const parentCanon = await this.importCanon(book.id, parentBookId);
664
+ const architect = new ArchitectAgent(this.agentCtxFor("architect", book.id));
665
+ const reviewer = new FoundationReviewerAgent(this.agentCtxFor("foundation-reviewer", book.id));
666
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
667
+ const resolvedLanguage = (book.language ?? gp.language) === "en" ? "en" : "zh";
668
+ const spinoffContext = buildSpinoffFoundationContext(parentCanon, direction, resolvedLanguage);
669
+ this.logStage(stageLanguage, { zh: "生成番外基础设定", en: "generating side-story foundation" });
670
+ const foundation = await this.generateAndReviewFoundation({
671
+ generate: (reviewFeedback) => architect.generateFoundation(book, spinoffContext, reviewFeedback),
672
+ reviewer,
673
+ mode: "original",
674
+ language: resolvedLanguage,
675
+ stageLanguage,
676
+ targetChapters: book.targetChapters,
677
+ });
678
+ this.logStage(stageLanguage, { zh: "写入基础设定文件", en: "writing foundation files" });
679
+ await architect.writeFoundationFiles(bookDir, foundation, gp.numericalSystem, book.language ?? gp.language);
680
+ this.logStage(stageLanguage, { zh: "初始化控制文档", en: "initializing control documents" });
681
+ await this.state.ensureControlDocuments(book.id, direction?.trim() || this.config.externalContext);
682
+ this.logStage(stageLanguage, { zh: "创建初始快照", en: "creating initial snapshot" });
683
+ await mkdir(join(bookDir, "chapters"), { recursive: true });
684
+ await this.state.saveChapterIndex(book.id, []);
685
+ await this.state.snapshotState(book.id, 0);
686
+ }
687
+ /**
688
+ * Create an imitation (仿写) book: an ORIGINAL story whose prose imitates the
689
+ * voice of a reference work. The architect builds an original foundation from
690
+ * the user's story idea; the reference text becomes the book's style_guide.md
691
+ * so the writer mimics its style. The style guide is mandatory here (imitation
692
+ * is the whole point), so a failure to generate it surfaces rather than being
693
+ * silently skipped.
694
+ */
695
+ async initImitationBook(book, referenceText, storyIdea, sourceName) {
696
+ await this.initBook(book, { externalContext: storyIdea });
697
+ const stageLanguage = await this.resolveBookLanguage(book);
698
+ this.logStage(stageLanguage, { zh: "提取参考作品风格指纹", en: "extracting reference style fingerprint" });
699
+ await this.generateStyleGuide(book.id, referenceText, sourceName?.trim() || "reference");
700
+ }
701
+ /** Write a single draft chapter. Saves chapter file + truth files + index + snapshot. */
702
+ async writeDraft(bookId, context, wordCount) {
703
+ const releaseLock = await this.state.acquireBookLock(bookId);
704
+ try {
705
+ await this.state.ensureControlDocuments(bookId);
706
+ const book = await this.state.loadBookConfig(bookId);
707
+ const bookDir = this.state.bookDir(bookId);
708
+ const chapterNumber = await this.state.getNextChapterNumber(bookId);
709
+ const stageLanguage = await this.resolveBookLanguage(book);
710
+ this.logStage(stageLanguage, { zh: "准备章节输入", en: "preparing chapter inputs" });
711
+ const writeInput = await this.prepareWriteInput(book, bookDir, chapterNumber, context ?? this.config.externalContext);
712
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
713
+ const lengthSpec = buildLengthSpec(wordCount ?? book.chapterWordCount, book.language ?? gp.language);
714
+ const writer = new WriterAgent(this.agentCtxFor("writer", bookId));
715
+ this.logStage(stageLanguage, { zh: "撰写章节草稿", en: "writing chapter draft" });
716
+ const output = await writer.writeChapter({
717
+ book,
718
+ bookDir,
719
+ chapterNumber,
720
+ ...writeInput,
721
+ lengthSpec,
722
+ ...(wordCount ? { wordCountOverride: wordCount } : {}),
723
+ });
724
+ const writerCount = countChapterLength(output.content, lengthSpec.countingMode);
725
+ const totalUsage = output.tokenUsage ?? {
726
+ promptTokens: 0,
727
+ completionTokens: 0,
728
+ totalTokens: 0,
729
+ };
730
+ const draftOutput = {
731
+ ...output,
732
+ wordCount: writerCount,
733
+ tokenUsage: totalUsage,
734
+ };
735
+ const lengthWarnings = this.buildLengthWarnings(chapterNumber, draftOutput.wordCount, lengthSpec);
736
+ const lengthTelemetry = this.buildLengthTelemetry({
737
+ lengthSpec,
738
+ writerCount,
739
+ postReviseCount: 0,
740
+ finalCount: draftOutput.wordCount,
741
+ repairApplied: false,
742
+ lengthWarning: lengthWarnings.length > 0,
743
+ });
744
+ this.logLengthWarnings(lengthWarnings);
745
+ // Save chapter file
746
+ const chaptersDir = join(bookDir, "chapters");
747
+ const paddedNum = String(chapterNumber).padStart(4, "0");
748
+ const sanitized = draftOutput.title.replace(/[/\\?%*:|"<>]/g, "").replace(/\s+/g, "_").slice(0, 50);
749
+ const filename = `${paddedNum}_${sanitized}.md`;
750
+ const filePath = join(chaptersDir, filename);
751
+ const resolvedLang = book.language ?? gp.language;
752
+ // Persist the chapter and its complete truth update as one atomic file set.
753
+ this.logStage(stageLanguage, { zh: "落盘草稿与真相文件", en: "persisting draft and truth files" });
754
+ await writer.saveChapter(bookDir, draftOutput, gp.numericalSystem, resolvedLang);
755
+ await this.syncLegacyStructuredStateFromMarkdown(bookDir, chapterNumber, draftOutput);
756
+ await this.syncNarrativeMemoryIndex(bookId);
757
+ // Update index
758
+ const existingIndex = await this.state.loadChapterIndex(bookId);
759
+ const now = new Date().toISOString();
760
+ const newEntry = {
761
+ number: chapterNumber,
762
+ title: draftOutput.title,
763
+ status: "drafted",
764
+ wordCount: draftOutput.wordCount,
765
+ createdAt: now,
766
+ updatedAt: now,
767
+ auditIssues: [],
768
+ lengthWarnings,
769
+ lengthTelemetry,
770
+ ...(draftOutput.tokenUsage ? { tokenUsage: draftOutput.tokenUsage } : {}),
771
+ };
772
+ const existingIdx = existingIndex.findIndex((e) => e.number === chapterNumber);
773
+ const updatedIndex = existingIdx >= 0
774
+ ? existingIndex.map((e, i) => i === existingIdx ? newEntry : e)
775
+ : [...existingIndex, newEntry];
776
+ await this.state.saveChapterIndex(bookId, updatedIndex);
777
+ await this.markBookActiveIfNeeded(bookId);
778
+ // Snapshot
779
+ this.logStage(stageLanguage, { zh: "更新章节索引与快照", en: "updating chapter index and snapshots" });
780
+ await this.state.snapshotState(bookId, chapterNumber);
781
+ await this.syncCurrentStateFactHistory(bookId, chapterNumber);
782
+ await this.emitWebhook("chapter-complete", bookId, chapterNumber, {
783
+ title: draftOutput.title,
784
+ wordCount: draftOutput.wordCount,
785
+ });
786
+ return {
787
+ chapterNumber,
788
+ title: draftOutput.title,
789
+ wordCount: draftOutput.wordCount,
790
+ filePath,
791
+ lengthWarnings,
792
+ lengthTelemetry,
793
+ tokenUsage: draftOutput.tokenUsage,
794
+ };
795
+ }
796
+ finally {
797
+ await releaseLock();
798
+ }
799
+ }
800
+ async planChapter(bookId, context) {
801
+ await this.state.ensureControlDocuments(bookId);
802
+ const book = await this.state.loadBookConfig(bookId);
803
+ const bookDir = this.state.bookDir(bookId);
804
+ const chapterNumber = await this.state.getNextChapterNumber(bookId);
805
+ const stageLanguage = await this.resolveBookLanguage(book);
806
+ this.logStage(stageLanguage, { zh: "规划下一章意图", en: "planning next chapter intent" });
807
+ const { plan } = await this.createGovernedArtifacts(book, bookDir, chapterNumber, context ?? this.config.externalContext, { reuseExistingIntentWhenContextMissing: false });
808
+ return {
809
+ bookId,
810
+ chapterNumber,
811
+ intentPath: relativeToBookDir(bookDir, plan.runtimePath),
812
+ goal: plan.intent.goal,
813
+ conflicts: [],
814
+ };
815
+ }
816
+ async composeChapter(bookId, context) {
817
+ await this.state.ensureControlDocuments(bookId);
818
+ const book = await this.state.loadBookConfig(bookId);
819
+ const bookDir = this.state.bookDir(bookId);
820
+ const chapterNumber = await this.state.getNextChapterNumber(bookId);
821
+ const stageLanguage = await this.resolveBookLanguage(book);
822
+ this.logStage(stageLanguage, { zh: "组装章节运行时上下文", en: "composing chapter runtime context" });
823
+ const { plan, composed } = await this.createGovernedArtifacts(book, bookDir, chapterNumber, context ?? this.config.externalContext, { reuseExistingIntentWhenContextMissing: true });
824
+ return {
825
+ bookId,
826
+ chapterNumber,
827
+ intentPath: relativeToBookDir(bookDir, plan.runtimePath),
828
+ goal: plan.intent.goal,
829
+ conflicts: [],
830
+ contextPath: relativeToBookDir(bookDir, composed.contextPath),
831
+ ruleStackPath: relativeToBookDir(bookDir, composed.ruleStackPath),
832
+ tracePath: relativeToBookDir(bookDir, composed.tracePath),
833
+ };
834
+ }
835
+ /** Audit the latest (or specified) chapter. Read-only, no lock needed. */
836
+ async auditDraft(bookId, chapterNumber) {
837
+ const book = await this.state.loadBookConfig(bookId);
838
+ const bookDir = this.state.bookDir(bookId);
839
+ const targetChapter = chapterNumber ?? (await this.state.getNextChapterNumber(bookId)) - 1;
840
+ if (targetChapter < 1) {
841
+ throw new Error(`No chapters to audit for "${bookId}"`);
842
+ }
843
+ const content = await this.readChapterContent(bookDir, targetChapter);
844
+ const auditor = new ContinuityAuditor(this.agentCtxFor("auditor", bookId));
845
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
846
+ const language = book.language ?? gp.language;
847
+ this.logStage(language, {
848
+ zh: `审计第${targetChapter}章`,
849
+ en: `auditing chapter ${targetChapter}`,
850
+ });
851
+ const evaluation = await this.evaluateMergedAudit({
852
+ auditor,
853
+ book,
854
+ bookDir,
855
+ chapterContent: content,
856
+ chapterNumber: targetChapter,
857
+ language,
858
+ });
859
+ const result = evaluation.auditResult;
860
+ // Update index with audit result
861
+ const index = await this.state.loadChapterIndex(bookId);
862
+ const updated = index.map((ch) => ch.number === targetChapter
863
+ ? {
864
+ ...ch,
865
+ status: (result.passed ? "ready-for-review" : "audit-failed"),
866
+ updatedAt: new Date().toISOString(),
867
+ auditIssues: result.issues.map((i) => `[${i.severity}] ${i.description}`),
868
+ }
869
+ : ch);
870
+ await this.state.saveChapterIndex(bookId, updated);
871
+ const latestChapter = index.length > 0 ? Math.max(...index.map((chapter) => chapter.number)) : targetChapter;
872
+ if (targetChapter === latestChapter) {
873
+ await this.persistAuditDriftGuidance({
874
+ bookDir,
875
+ chapterNumber: targetChapter,
876
+ issues: result.issues.filter((issue) => issue.severity === "critical" || issue.severity === "warning"),
877
+ language,
878
+ }).catch(() => undefined);
879
+ }
880
+ await this.emitWebhook(result.passed ? "audit-passed" : "audit-failed", bookId, targetChapter, { summary: result.summary, issueCount: result.issues.length });
881
+ return { ...result, chapterNumber: targetChapter };
882
+ }
883
+ /** Revise the latest (or specified) chapter based on audit issues. */
884
+ async reviseDraft(bookId, chapterNumber, mode = DEFAULT_REVISE_MODE, externalContext) {
885
+ const releaseLock = await this.state.acquireBookLock(bookId);
886
+ try {
887
+ const book = await this.state.loadBookConfig(bookId);
888
+ const bookDir = this.state.bookDir(bookId);
889
+ const targetChapter = chapterNumber ?? (await this.state.getNextChapterNumber(bookId)) - 1;
890
+ if (targetChapter < 1) {
891
+ throw new Error(`No chapters to revise for "${bookId}"`);
892
+ }
893
+ const stageLanguage = await this.resolveBookLanguage(book);
894
+ // Read the current audit issues from index
895
+ this.logStage(stageLanguage, {
896
+ zh: `加载第${targetChapter}章修订上下文`,
897
+ en: `loading revision context for chapter ${targetChapter}`,
898
+ });
899
+ const index = await this.state.loadChapterIndex(bookId);
900
+ const chapterMeta = index.find((ch) => ch.number === targetChapter);
901
+ if (!chapterMeta) {
902
+ throw new Error(`Chapter ${targetChapter} not found in index`);
903
+ }
904
+ const latestChapter = index.length > 0
905
+ ? Math.max(...index.map((chapter) => chapter.number))
906
+ : targetChapter;
907
+ const isLatestChapter = targetChapter === latestChapter;
908
+ // Re-audit to get structured issues (index only stores strings)
909
+ const content = await this.readChapterContent(bookDir, targetChapter);
910
+ const auditor = new ContinuityAuditor(this.agentCtxFor("auditor", bookId));
911
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
912
+ const language = book.language ?? gp.language;
913
+ const countingMode = resolveLengthCountingMode(language);
914
+ const persistedChapterBrief = await readChapterUserBrief(bookDir, targetChapter);
915
+ const effectiveExternalContext = mergeChapterRevisionInstructions(persistedChapterBrief, externalContext ?? this.config.externalContext);
916
+ const reviseControlInput = await this.createGovernedArtifacts(book, bookDir, targetChapter, effectiveExternalContext, { reuseExistingIntentWhenContextMissing: true });
917
+ const preRevision = await this.evaluateMergedAudit({
918
+ auditor,
919
+ book,
920
+ bookDir,
921
+ chapterContent: content,
922
+ chapterNumber: targetChapter,
923
+ language,
924
+ auditOptions: reviseControlInput
925
+ ? {
926
+ chapterIntent: reviseControlInput.plan.intentMarkdown,
927
+ chapterMemo: reviseControlInput.plan.memo,
928
+ contextPackage: reviseControlInput.composed.contextPackage,
929
+ ruleStack: reviseControlInput.composed.ruleStack,
930
+ }
931
+ : undefined,
932
+ });
933
+ const explicitRevisionRequested = Boolean(effectiveExternalContext?.trim())
934
+ || mode === "rewrite"
935
+ || mode === "rework";
936
+ if (preRevision.blockingCount === 0
937
+ && preRevision.aiTellCount === 0
938
+ && !explicitRevisionRequested) {
939
+ return {
940
+ chapterNumber: targetChapter,
941
+ wordCount: countChapterLength(content, countingMode),
942
+ fixedIssues: [],
943
+ applied: false,
944
+ status: "unchanged",
945
+ skippedReason: "No warning, critical, or AI-tell issues to fix.",
946
+ };
947
+ }
948
+ const chapterLengthTarget = chapterMeta.lengthTelemetry?.target ?? book.chapterWordCount;
949
+ const lengthLanguage = chapterMeta.lengthTelemetry?.countingMode === "en_words"
950
+ ? "en"
951
+ : language;
952
+ const lengthSpec = buildLengthSpec(chapterLengthTarget, lengthLanguage);
953
+ const baselineChapter = targetChapter - 1;
954
+ const baselineStoryDir = join(bookDir, "story", "snapshots", String(baselineChapter));
955
+ const [baselineState, baselineHooks] = await Promise.all([
956
+ readFile(join(baselineStoryDir, "current_state.md"), "utf-8"),
957
+ readFile(join(baselineStoryDir, "pending_hooks.md"), "utf-8"),
958
+ ]).catch((error) => {
959
+ throw new Error(`Cannot revise chapter ${targetChapter} safely: baseline snapshot ${baselineChapter} is unavailable (${String(error)})`);
960
+ });
961
+ const reviser = new ReviserAgent(this.agentCtxFor("reviser", bookId));
962
+ this.logStage(stageLanguage, {
963
+ zh: `修订第${targetChapter}章`,
964
+ en: `revising chapter ${targetChapter}`,
965
+ });
966
+ const reviseOutput = await reviser.reviseChapter(bookDir, content, targetChapter, preRevision.auditResult.issues, mode, book.genre, reviseControlInput
967
+ ? {
968
+ chapterIntent: reviseControlInput.plan.intentMarkdown,
969
+ chapterMemo: reviseControlInput.plan.memo,
970
+ chapterIntentData: reviseControlInput.plan.intent,
971
+ contextPackage: reviseControlInput.composed.contextPackage,
972
+ ruleStack: reviseControlInput.composed.ruleStack,
973
+ lengthSpec,
974
+ baselineChapter,
975
+ }
976
+ : { lengthSpec, baselineChapter });
977
+ if (reviseOutput.revisedContent.length === 0) {
978
+ throw new Error("Reviser returned empty content");
979
+ }
980
+ const revisedContent = reviseOutput.revisedContent;
981
+ const revisedCount = countChapterLength(revisedContent, lengthSpec.countingMode);
982
+ const writer = new WriterAgent(this.agentCtxFor("writer", bookId));
983
+ const stateValidator = new StateValidatorAgent(this.agentCtxFor("stateValidator", bookId));
984
+ let settledRevision = await writer.settleChapterState({
985
+ book,
986
+ bookDir,
987
+ chapterNumber: targetChapter,
988
+ baselineChapter,
989
+ title: chapterMeta.title,
990
+ content: revisedContent,
991
+ chapterIntent: reviseControlInput?.plan.intentMarkdown,
992
+ contextPackage: reviseControlInput?.composed.contextPackage,
993
+ ruleStack: reviseControlInput?.composed.ruleStack,
994
+ });
995
+ let stateValidation = await stateValidator.validate(revisedContent, targetChapter, baselineState, settledRevision.updatedState, baselineHooks, settledRevision.updatedHooks, language);
996
+ if (!stateValidation.passed || stateValidation.repairRequired) {
997
+ const recovery = await retrySettlementAfterValidationFailure({
998
+ writer,
999
+ validator: stateValidator,
1000
+ book,
1001
+ bookDir,
1002
+ chapterNumber: targetChapter,
1003
+ baselineChapter,
1004
+ title: chapterMeta.title,
1005
+ content: revisedContent,
1006
+ reducedControlInput: reviseControlInput
1007
+ ? {
1008
+ chapterIntent: reviseControlInput.plan.intentMarkdown,
1009
+ contextPackage: reviseControlInput.composed.contextPackage,
1010
+ ruleStack: reviseControlInput.composed.ruleStack,
1011
+ }
1012
+ : undefined,
1013
+ oldState: baselineState,
1014
+ oldHooks: baselineHooks,
1015
+ originalValidation: stateValidation,
1016
+ language,
1017
+ logger: this.config.logger,
1018
+ });
1019
+ if (recovery.kind === "degraded") {
1020
+ return {
1021
+ chapterNumber: targetChapter,
1022
+ wordCount: countChapterLength(content, countingMode),
1023
+ fixedIssues: [],
1024
+ applied: false,
1025
+ status: "unchanged",
1026
+ auditPassed: false,
1027
+ auditIssues: recovery.issues,
1028
+ skippedReason: `Revision kept the original chapter because state settlement did not validate after retry.`,
1029
+ revisionDiagnostics: {
1030
+ standard: "Revision text and derived story state must both validate before any file is replaced.",
1031
+ before: {
1032
+ blockingCount: preRevision.blockingCount,
1033
+ criticalCount: preRevision.criticalCount,
1034
+ aiTellCount: preRevision.aiTellCount,
1035
+ },
1036
+ after: {
1037
+ blockingCount: preRevision.blockingCount,
1038
+ criticalCount: preRevision.criticalCount,
1039
+ aiTellCount: preRevision.aiTellCount,
1040
+ },
1041
+ remainingIssues: recovery.issues,
1042
+ },
1043
+ };
1044
+ }
1045
+ settledRevision = recovery.output;
1046
+ stateValidation = recovery.validation;
1047
+ }
1048
+ const postRevision = await this.evaluateMergedAudit({
1049
+ auditor,
1050
+ book,
1051
+ bookDir,
1052
+ chapterContent: revisedContent,
1053
+ chapterNumber: targetChapter,
1054
+ language,
1055
+ auditOptions: reviseControlInput
1056
+ ? {
1057
+ temperature: 0,
1058
+ chapterIntent: reviseControlInput.plan.intentMarkdown,
1059
+ chapterMemo: reviseControlInput.plan.memo,
1060
+ contextPackage: reviseControlInput.composed.contextPackage,
1061
+ ruleStack: reviseControlInput.composed.ruleStack,
1062
+ truthFileOverrides: {
1063
+ currentState: settledRevision.updatedState,
1064
+ ledger: settledRevision.updatedLedger || undefined,
1065
+ hooks: settledRevision.updatedHooks,
1066
+ },
1067
+ }
1068
+ : {
1069
+ temperature: 0,
1070
+ truthFileOverrides: {
1071
+ currentState: settledRevision.updatedState,
1072
+ ledger: settledRevision.updatedLedger || undefined,
1073
+ hooks: settledRevision.updatedHooks,
1074
+ },
1075
+ },
1076
+ });
1077
+ const effectivePostRevision = this.restoreActionableAuditIfLost(preRevision, postRevision);
1078
+ const revisionBaseCount = countChapterLength(content, lengthSpec.countingMode);
1079
+ const lengthWarnings = this.buildLengthWarnings(targetChapter, revisedCount, lengthSpec);
1080
+ const lengthTelemetry = this.buildLengthTelemetry({
1081
+ lengthSpec,
1082
+ writerCount: revisionBaseCount,
1083
+ postReviseCount: revisedCount,
1084
+ finalCount: revisedCount,
1085
+ repairApplied: revisedContent !== content,
1086
+ lengthWarning: lengthWarnings.length > 0,
1087
+ });
1088
+ const improvedBlocking = effectivePostRevision.blockingCount < preRevision.blockingCount;
1089
+ const improvedAITells = effectivePostRevision.aiTellCount < preRevision.aiTellCount;
1090
+ const blockingDidNotWorsen = effectivePostRevision.blockingCount <= preRevision.blockingCount;
1091
+ const criticalDidNotWorsen = effectivePostRevision.criticalCount <= preRevision.criticalCount;
1092
+ const aiDidNotWorsen = effectivePostRevision.aiTellCount <= preRevision.aiTellCount;
1093
+ const didNotWorsen = blockingDidNotWorsen && criticalDidNotWorsen && aiDidNotWorsen;
1094
+ const revisionGate = this.config.revisionGate ?? "strict";
1095
+ const shouldApplyRevision = revisionGate === "always"
1096
+ ? true
1097
+ : revisionGate === "lenient"
1098
+ ? didNotWorsen
1099
+ : didNotWorsen && (improvedBlocking || improvedAITells);
1100
+ const remainingIssues = effectivePostRevision.revisionBlockingIssues
1101
+ .filter((issue) => issue.severity === "warning" || issue.severity === "critical")
1102
+ .slice(0, 6)
1103
+ .map((issue) => ({
1104
+ severity: issue.severity,
1105
+ category: issue.category,
1106
+ description: issue.description,
1107
+ ...(issue.suggestion ? { suggestion: issue.suggestion } : {}),
1108
+ }));
1109
+ const revisionDiagnostics = {
1110
+ standard: REVISION_GATE_STANDARDS[revisionGate],
1111
+ before: {
1112
+ blockingCount: preRevision.blockingCount,
1113
+ criticalCount: preRevision.criticalCount,
1114
+ aiTellCount: preRevision.aiTellCount,
1115
+ },
1116
+ after: {
1117
+ blockingCount: effectivePostRevision.blockingCount,
1118
+ criticalCount: effectivePostRevision.criticalCount,
1119
+ aiTellCount: effectivePostRevision.aiTellCount,
1120
+ },
1121
+ remainingIssues,
1122
+ };
1123
+ if (!shouldApplyRevision) {
1124
+ return {
1125
+ chapterNumber: targetChapter,
1126
+ wordCount: revisionBaseCount,
1127
+ fixedIssues: [],
1128
+ applied: false,
1129
+ status: "unchanged",
1130
+ skippedReason: `Manual revision kept original chapter: before blocking=${preRevision.blockingCount}, critical=${preRevision.criticalCount}, aiTell=${preRevision.aiTellCount}; after blocking=${effectivePostRevision.blockingCount}, critical=${effectivePostRevision.criticalCount}, aiTell=${effectivePostRevision.aiTellCount}.`,
1131
+ auditPassed: effectivePostRevision.auditResult.passed,
1132
+ auditIssues: remainingIssues,
1133
+ revisionDiagnostics,
1134
+ };
1135
+ }
1136
+ this.logLengthWarnings(lengthWarnings);
1137
+ // Save revised chapter file
1138
+ this.logStage(stageLanguage, {
1139
+ zh: `落盘第${targetChapter}章修订结果`,
1140
+ en: `persisting revision for chapter ${targetChapter}`,
1141
+ });
1142
+ const chaptersDir = join(bookDir, "chapters");
1143
+ const files = await readdir(chaptersDir);
1144
+ const paddedNum = String(targetChapter).padStart(4, "0");
1145
+ const existingFile = files.find((f) => f.startsWith(paddedNum) && f.endsWith(".md"));
1146
+ if (!existingFile) {
1147
+ throw new Error(`Chapter ${targetChapter} file not found in ${chaptersDir} (expected filename starting with ${paddedNum})`);
1148
+ }
1149
+ await archiveChapterVersion(bookDir, targetChapter, content, "revision");
1150
+ const reviseLang = book.language ?? gp.language;
1151
+ const reviseHeading = reviseLang === "en"
1152
+ ? `# Chapter ${targetChapter}: ${chapterMeta.title}`
1153
+ : `# 第${targetChapter}章 ${chapterMeta.title}`;
1154
+ // Only the latest chapter owns current truth. Reworking an older chapter
1155
+ // invalidates its descendants, but must not rewind the live story state.
1156
+ if (isLatestChapter) {
1157
+ await writer.saveChapter(bookDir, settledRevision, gp.numericalSystem, reviseLang);
1158
+ }
1159
+ else {
1160
+ await commitAtomicFileSet({
1161
+ rootDir: bookDir,
1162
+ writes: [{
1163
+ relativePath: join("chapters", existingFile),
1164
+ content: `${reviseHeading}\n\n${revisedContent}`,
1165
+ }],
1166
+ });
1167
+ }
1168
+ // Update index
1169
+ const downstreamRevisionNotice = language === "en"
1170
+ ? `[warning] Chapter ${targetChapter} changed; re-review this downstream chapter for continuity.`
1171
+ : `[warning] 第${targetChapter}章已重写,请重新检查本章与前文的连续性。`;
1172
+ const updatedIndex = index.map((ch) => {
1173
+ if (ch.number === targetChapter) {
1174
+ return {
1175
+ ...ch,
1176
+ status: (effectivePostRevision.auditResult.passed ? "ready-for-review" : "audit-failed"),
1177
+ wordCount: revisedCount,
1178
+ updatedAt: new Date().toISOString(),
1179
+ auditIssues: effectivePostRevision.auditResult.issues.map((i) => `[${i.severity}] ${i.description}`),
1180
+ lengthWarnings,
1181
+ lengthTelemetry,
1182
+ };
1183
+ }
1184
+ if (ch.number > targetChapter) {
1185
+ return {
1186
+ ...ch,
1187
+ status: "needs-revision",
1188
+ updatedAt: new Date().toISOString(),
1189
+ auditIssues: [
1190
+ ...(ch.auditIssues ?? []).filter((issue) => !issue.includes("re-review this downstream chapter") && !issue.includes("请重新检查本章与前文")),
1191
+ downstreamRevisionNotice,
1192
+ ],
1193
+ };
1194
+ }
1195
+ return ch;
1196
+ });
1197
+ await this.state.saveChapterIndex(bookId, updatedIndex);
1198
+ if (isLatestChapter) {
1199
+ await this.persistAuditDriftGuidance({
1200
+ bookDir,
1201
+ chapterNumber: targetChapter,
1202
+ issues: effectivePostRevision.auditResult.issues.filter((issue) => issue.severity === "critical" || issue.severity === "warning"),
1203
+ language,
1204
+ }).catch(() => undefined);
1205
+ }
1206
+ // Re-snapshot
1207
+ this.logStage(stageLanguage, {
1208
+ zh: `更新第${targetChapter}章索引与快照`,
1209
+ en: `updating chapter index and snapshots for chapter ${targetChapter}`,
1210
+ });
1211
+ if (isLatestChapter) {
1212
+ await this.state.snapshotState(bookId, targetChapter);
1213
+ }
1214
+ await this.syncNarrativeMemoryIndex(bookId);
1215
+ if (isLatestChapter) {
1216
+ await this.syncCurrentStateFactHistory(bookId, targetChapter);
1217
+ }
1218
+ await this.emitWebhook("revision-complete", bookId, targetChapter, {
1219
+ wordCount: revisedCount,
1220
+ fixedCount: reviseOutput.fixedIssues.length,
1221
+ });
1222
+ return {
1223
+ chapterNumber: targetChapter,
1224
+ wordCount: revisedCount,
1225
+ fixedIssues: reviseOutput.fixedIssues,
1226
+ applied: true,
1227
+ status: effectivePostRevision.auditResult.passed ? "ready-for-review" : "audit-failed",
1228
+ auditPassed: effectivePostRevision.auditResult.passed,
1229
+ auditIssues: remainingIssues,
1230
+ revisionDiagnostics,
1231
+ lengthWarnings,
1232
+ lengthTelemetry,
1233
+ };
1234
+ }
1235
+ finally {
1236
+ await releaseLock();
1237
+ }
1238
+ }
1239
+ /** Read all truth files for a book. */
1240
+ async readTruthFiles(bookId) {
1241
+ const bookDir = this.state.bookDir(bookId);
1242
+ const storyDir = join(bookDir, "story");
1243
+ const readSafe = async (path) => {
1244
+ try {
1245
+ return await readFile(path, "utf-8");
1246
+ }
1247
+ catch {
1248
+ return "(文件不存在)";
1249
+ }
1250
+ };
1251
+ // Phase 5: prefer the new prose outline files; fall back to legacy paths.
1252
+ const readOutline = async (newRel, legacyRel) => {
1253
+ const preferred = await readSafe(join(storyDir, newRel));
1254
+ if (preferred.trim() && preferred !== "(文件不存在)")
1255
+ return preferred;
1256
+ return readSafe(join(storyDir, legacyRel));
1257
+ };
1258
+ const [currentState, particleLedger, pendingHooks, storyBible, volumeOutline, bookRules] = await Promise.all([
1259
+ readSafe(join(storyDir, "current_state.md")),
1260
+ readSafe(join(storyDir, "particle_ledger.md")),
1261
+ readSafe(join(storyDir, "pending_hooks.md")),
1262
+ readOutline("outline/story_frame.md", "story_bible.md"),
1263
+ readOutline("outline/volume_map.md", "volume_outline.md"),
1264
+ readSafe(join(storyDir, "book_rules.md")),
1265
+ ]);
1266
+ return { currentState, particleLedger, pendingHooks, storyBible, volumeOutline, bookRules };
1267
+ }
1268
+ /** Get book status overview. */
1269
+ async getBookStatus(bookId) {
1270
+ const book = await this.state.loadBookConfig(bookId);
1271
+ const chapters = await this.state.loadChapterIndex(bookId);
1272
+ const nextChapter = await this.state.getNextChapterNumber(bookId);
1273
+ const totalWords = chapters.reduce((sum, ch) => sum + ch.wordCount, 0);
1274
+ return {
1275
+ bookId,
1276
+ title: book.title,
1277
+ genre: book.genre,
1278
+ platform: book.platform,
1279
+ status: book.status,
1280
+ chaptersWritten: chapters.length,
1281
+ totalWords,
1282
+ nextChapter,
1283
+ chapters: [...chapters],
1284
+ };
1285
+ }
1286
+ // ---------------------------------------------------------------------------
1287
+ // Full pipeline (convenience — runs draft + audit + revise in one shot)
1288
+ // ---------------------------------------------------------------------------
1289
+ async writeNextChapter(bookId, wordCount, temperatureOverride, externalContext) {
1290
+ this.throwIfOperationAborted();
1291
+ const releaseLock = await this.state.acquireBookLock(bookId);
1292
+ try {
1293
+ return await this._writeNextChapterLocked(bookId, wordCount, temperatureOverride, externalContext ?? this.config.externalContext);
1294
+ }
1295
+ finally {
1296
+ await releaseLock();
1297
+ }
1298
+ }
1299
+ async writeChapters(bookId, chapterCount, options = {}) {
1300
+ if (!Number.isInteger(chapterCount) || chapterCount < 1 || chapterCount > 20) {
1301
+ throw new Error(`chapterCount must be an integer between 1 and 20; received ${chapterCount}.`);
1302
+ }
1303
+ this.throwIfOperationAborted();
1304
+ const releaseLock = await this.state.acquireBookLock(bookId);
1305
+ try {
1306
+ const results = [];
1307
+ for (let index = 0; index < chapterCount; index += 1) {
1308
+ this.throwIfOperationAborted();
1309
+ const result = await this._writeNextChapterLocked(bookId, options.wordCount, options.temperatureOverride, options.externalContext ?? this.config.externalContext);
1310
+ results.push(result);
1311
+ options.onChapterComplete?.(result, results.length, chapterCount);
1312
+ if (result.status !== "ready-for-review")
1313
+ break;
1314
+ }
1315
+ return results;
1316
+ }
1317
+ finally {
1318
+ await releaseLock();
1319
+ }
1320
+ }
1321
+ async repairChapterState(bookId, chapterNumber) {
1322
+ const releaseLock = await this.state.acquireBookLock(bookId);
1323
+ try {
1324
+ return await this._repairChapterStateLocked(bookId, chapterNumber);
1325
+ }
1326
+ finally {
1327
+ await releaseLock();
1328
+ }
1329
+ }
1330
+ async resyncChapterArtifacts(bookId, chapterNumber) {
1331
+ const releaseLock = await this.state.acquireBookLock(bookId);
1332
+ try {
1333
+ return await this._resyncChapterArtifactsLocked(bookId, chapterNumber);
1334
+ }
1335
+ finally {
1336
+ await releaseLock();
1337
+ }
1338
+ }
1339
+ async resyncChapterStateAndAudit(bookId, chapterNumber, options = {}) {
1340
+ const releaseLock = await this.state.acquireBookLock(bookId);
1341
+ try {
1342
+ const chapter = await this._resyncChapterArtifactsLocked(bookId, chapterNumber, options);
1343
+ const audit = await this.auditDraft(bookId, chapter.chapterNumber);
1344
+ return { chapter, audit };
1345
+ }
1346
+ finally {
1347
+ await releaseLock();
1348
+ }
1349
+ }
1350
+ async _writeNextChapterLocked(bookId, wordCount, temperatureOverride, externalContext) {
1351
+ const book = await this.state.loadBookConfig(bookId);
1352
+ const bookDir = this.state.bookDir(bookId);
1353
+ const chapterNumber = await this.state.getNextChapterNumber(bookId);
1354
+ const paddedChapter = String(chapterNumber).padStart(4, "0");
1355
+ const runPath = join("story", "runtime", `chapter-${paddedChapter}.run.json`);
1356
+ const runId = `${bookId}:chapter-${paddedChapter}`;
1357
+ const baseRun = {
1358
+ kind: "long-fiction",
1359
+ id: runId,
1360
+ stage: `chapter-${chapterNumber}`,
1361
+ model: this.config.model,
1362
+ skillIds: ["inkos-long-writing"],
1363
+ resumeCursor: String(chapterNumber),
1364
+ };
1365
+ await writeProductionRunSnapshot({
1366
+ rootDir: bookDir,
1367
+ runPath,
1368
+ run: createProductionRunSnapshot({
1369
+ ...baseRun,
1370
+ status: "running",
1371
+ artifacts: [],
1372
+ observations: [],
1373
+ }),
1374
+ });
1375
+ try {
1376
+ const result = await this._executeNextChapterLocked(bookId, wordCount, temperatureOverride, externalContext);
1377
+ const chapterPrefix = `${paddedChapter}_`;
1378
+ const chapterFile = (await readdir(join(bookDir, "chapters")))
1379
+ .find((file) => file.startsWith(chapterPrefix) && file.endsWith(".md"));
1380
+ if (!chapterFile) {
1381
+ throw new Error(`Chapter ${chapterNumber} completed without a persisted chapter artifact.`);
1382
+ }
1383
+ const lengthSpec = result.lengthTelemetry ?? buildLengthSpec(wordCount ?? book.chapterWordCount, await this.resolveBookLanguage(book));
1384
+ const chapterPath = toPosixPath(join("chapters", chapterFile));
1385
+ const artifacts = [
1386
+ chapterPath,
1387
+ join("chapters", "index.json"),
1388
+ join("story", "current_state.md"),
1389
+ join("story", "pending_hooks.md"),
1390
+ join("story", "snapshots", String(chapterNumber)),
1391
+ join("story", "runtime", `chapter-${paddedChapter}.trace.json`),
1392
+ ].map(toPosixPath);
1393
+ await writeProductionRunSnapshot({
1394
+ rootDir: bookDir,
1395
+ runPath,
1396
+ run: createProductionRunSnapshot({
1397
+ ...baseRun,
1398
+ status: result.status === "ready-for-review" ? "complete" : "needs-review",
1399
+ artifacts,
1400
+ observations: [createRangeObservation({
1401
+ metric: "chapter-length",
1402
+ actual: result.wordCount,
1403
+ target: lengthSpec.target,
1404
+ min: lengthSpec.hardMin,
1405
+ max: lengthSpec.hardMax,
1406
+ unit: lengthSpec.countingMode,
1407
+ evidence: chapterPath,
1408
+ })],
1409
+ }),
1410
+ });
1411
+ return result;
1412
+ }
1413
+ catch (error) {
1414
+ const cancelled = this.currentAbortSignal()?.aborted === true;
1415
+ await writeProductionRunSnapshot({
1416
+ rootDir: bookDir,
1417
+ runPath,
1418
+ run: createProductionRunSnapshot({
1419
+ ...baseRun,
1420
+ status: cancelled ? "cancelled" : "failed",
1421
+ artifacts: [],
1422
+ observations: [],
1423
+ error: error instanceof Error ? error.message : String(error),
1424
+ }),
1425
+ }).catch(() => undefined);
1426
+ throw error;
1427
+ }
1428
+ }
1429
+ async _executeNextChapterLocked(bookId, wordCount, temperatureOverride, externalContext) {
1430
+ this.throwIfOperationAborted();
1431
+ await this.state.ensureControlDocuments(bookId);
1432
+ const book = await this.state.loadBookConfig(bookId);
1433
+ const bookDir = this.state.bookDir(bookId);
1434
+ await this.assertNoPendingStateRepair(bookId);
1435
+ const chapterNumber = await this.state.getNextChapterNumber(bookId);
1436
+ const stageLanguage = await this.resolveBookLanguage(book);
1437
+ this.logStage(stageLanguage, { zh: "准备章节输入", en: "preparing chapter inputs" });
1438
+ const writeInput = await this.prepareWriteInput(book, bookDir, chapterNumber, externalContext);
1439
+ const reducedControlInput = {
1440
+ chapterIntent: writeInput.chapterIntent,
1441
+ chapterMemo: writeInput.chapterMemo,
1442
+ chapterIntentData: writeInput.chapterIntentData,
1443
+ contextPackage: writeInput.contextPackage,
1444
+ ruleStack: writeInput.ruleStack,
1445
+ };
1446
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
1447
+ const pipelineLang = book.language ?? gp.language;
1448
+ const lengthSpec = buildLengthSpec(wordCount ?? book.chapterWordCount, pipelineLang);
1449
+ const { normalizePostWriteSurface, validatePostWrite: postWriteValidate, } = await import("../agents/post-write-validator.js");
1450
+ const { validateHookLedger } = await import("../utils/hook-ledger-validator.js");
1451
+ const { readBookRules } = await import("../agents/rules-reader.js");
1452
+ const parsedBookRules = (await readBookRules(bookDir))?.rules ?? null;
1453
+ // 1. Write chapter
1454
+ const writer = new WriterAgent(this.agentCtxFor("writer", bookId));
1455
+ this.logStage(stageLanguage, { zh: "撰写章节草稿", en: "writing chapter draft" });
1456
+ const output = await writer.writeChapter({
1457
+ book,
1458
+ bookDir,
1459
+ chapterNumber,
1460
+ ...writeInput,
1461
+ lengthSpec,
1462
+ ...(wordCount ? { wordCountOverride: wordCount } : {}),
1463
+ ...(temperatureOverride ? { temperatureOverride } : {}),
1464
+ });
1465
+ this.throwIfOperationAborted();
1466
+ const writerCount = countChapterLength(output.content, lengthSpec.countingMode);
1467
+ // Token usage accumulator
1468
+ let totalUsage = output.tokenUsage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
1469
+ let finalContent;
1470
+ let finalWordCount;
1471
+ let revised;
1472
+ let auditResult;
1473
+ let postReviseCount;
1474
+ let repairApplied;
1475
+ if ((this.config.chapterReviewMode ?? "auto") === "manual") {
1476
+ // C4a: write-only checkpoint. Stop right after the draft — skip the
1477
+ // automatic audit→revise loop (which silently doubled chapter time when it
1478
+ // fired). The user drives review / revise / accept afterwards.
1479
+ this.logStage(stageLanguage, { zh: "写完即停(手动审查模式)", en: "draft written — stopping for manual review" });
1480
+ finalContent = normalizePostWriteSurface(output.content, pipelineLang);
1481
+ this.assertChapterContentNotEmpty(finalContent, chapterNumber, "manual write");
1482
+ finalWordCount = countChapterLength(finalContent, lengthSpec.countingMode);
1483
+ revised = false;
1484
+ postReviseCount = 0;
1485
+ repairApplied = false;
1486
+ auditResult = {
1487
+ passed: false,
1488
+ issues: [],
1489
+ summary: pipelineLang === "en"
1490
+ ? "Not reviewed yet (manual mode: stopped after writing — run review when ready)."
1491
+ : "尚未审查(手动模式:写完即停,需要时点“审查”)。",
1492
+ };
1493
+ }
1494
+ else {
1495
+ const auditor = new ContinuityAuditor(this.agentCtxFor("auditor", bookId));
1496
+ const reviewResult = await runChapterReviewCycle({
1497
+ book: { genre: book.genre },
1498
+ bookDir,
1499
+ chapterNumber,
1500
+ initialOutput: output,
1501
+ reducedControlInput,
1502
+ lengthSpec,
1503
+ initialUsage: totalUsage,
1504
+ createReviser: () => new ReviserAgent(this.agentCtxFor("reviser", bookId)),
1505
+ auditor,
1506
+ normalizePostWriteSurface: (chapterContent) => normalizePostWriteSurface(chapterContent, pipelineLang),
1507
+ assertChapterContentNotEmpty: (content, stage) => this.assertChapterContentNotEmpty(content, chapterNumber, stage),
1508
+ addUsage: PipelineRunner.addUsage,
1509
+ analyzeAITells: (content) => analyzeAITells(content, pipelineLang),
1510
+ analyzeSensitiveWords: (content) => analyzeSensitiveWords(content, undefined, pipelineLang),
1511
+ runPostWriteChecks: (content) => {
1512
+ const baseIssues = postWriteValidate(content, gp, parsedBookRules, pipelineLang)
1513
+ .filter((v) => v.severity === "error")
1514
+ .map((v) => ({
1515
+ severity: "critical",
1516
+ category: v.rule,
1517
+ description: v.description,
1518
+ suggestion: v.suggestion,
1519
+ }));
1520
+ // Phase 9-3: verify the draft acts on every hook the memo committed to.
1521
+ const memoBody = writeInput.chapterMemo?.body ?? "";
1522
+ const ledgerIssues = memoBody
1523
+ ? validateHookLedger(memoBody, content)
1524
+ : [];
1525
+ return [...baseIssues, ...ledgerIssues];
1526
+ },
1527
+ maxReviewIterations: this.config.writingReviewRetries,
1528
+ logWarn: (message) => this.logWarn(pipelineLang, message),
1529
+ logStage: (message) => this.logStage(stageLanguage, message),
1530
+ });
1531
+ totalUsage = reviewResult.totalUsage;
1532
+ finalContent = reviewResult.finalContent;
1533
+ finalWordCount = reviewResult.finalWordCount;
1534
+ revised = reviewResult.revised;
1535
+ auditResult = reviewResult.auditResult;
1536
+ postReviseCount = reviewResult.postReviseCount;
1537
+ repairApplied = reviewResult.repairApplied;
1538
+ }
1539
+ this.throwIfOperationAborted();
1540
+ this.throwIfOperationAborted();
1541
+ // 4. Save the final chapter and truth files from a single persistence source
1542
+ this.logStage(stageLanguage, { zh: "落盘最终章节", en: "persisting final chapter" });
1543
+ this.logStage(stageLanguage, { zh: "生成最终真相文件", en: "rebuilding final truth files" });
1544
+ const chapterIndexBeforePersist = await this.state.loadChapterIndex(bookId);
1545
+ const { resolveDuplicateTitle } = await import("../agents/post-write-validator.js");
1546
+ const initialTitleResolution = resolveDuplicateTitle(output.title, chapterIndexBeforePersist.map((chapter) => chapter.title), pipelineLang, { content: finalContent });
1547
+ let persistenceOutput = await this.buildPersistenceOutput(bookId, book, bookDir, chapterNumber, initialTitleResolution.title === output.title
1548
+ ? output
1549
+ : { ...output, title: initialTitleResolution.title }, finalContent, lengthSpec.countingMode, reducedControlInput);
1550
+ const finalTitleResolution = resolveDuplicateTitle(persistenceOutput.title, chapterIndexBeforePersist.map((chapter) => chapter.title), pipelineLang, { content: finalContent });
1551
+ if (finalTitleResolution.title !== persistenceOutput.title) {
1552
+ persistenceOutput = {
1553
+ ...persistenceOutput,
1554
+ title: finalTitleResolution.title,
1555
+ };
1556
+ }
1557
+ {
1558
+ const { rerunPromotionPass } = await import("../utils/hook-promotion.js");
1559
+ const { parsePendingHooksMarkdown, renderHookSnapshot } = await import("../utils/story-markdown.js");
1560
+ const hooks = parsePendingHooksMarkdown(persistenceOutput.updatedHooks);
1561
+ const summaries = persistenceOutput.updatedChapterSummaries
1562
+ ?? await readFile(join(bookDir, "story", "chapter_summaries.md"), "utf-8").catch(() => "");
1563
+ const promotion = rerunPromotionPass(hooks, summaries);
1564
+ if (promotion.updated) {
1565
+ const renderedHooks = renderHookSnapshot([...promotion.hooks], pipelineLang);
1566
+ persistenceOutput = {
1567
+ ...persistenceOutput,
1568
+ updatedHooks: renderedHooks,
1569
+ ...(persistenceOutput.runtimeStateSnapshot ? {
1570
+ runtimeStateSnapshot: {
1571
+ ...persistenceOutput.runtimeStateSnapshot,
1572
+ hooks: HooksStateSchema.parse({ hooks: [...promotion.hooks] }),
1573
+ },
1574
+ } : {}),
1575
+ };
1576
+ this.config.logger?.info(`[promotion] ${promotion.flippedCount} hook(s) promoted after chapter ${chapterNumber}`);
1577
+ }
1578
+ }
1579
+ if (persistenceOutput.title !== output.title) {
1580
+ const description = pipelineLang === "en"
1581
+ ? `Chapter title "${output.title}" was auto-adjusted to "${persistenceOutput.title}".`
1582
+ : `章节标题"${output.title}"已自动调整为"${persistenceOutput.title}"。`;
1583
+ this.config.logger?.warn(`[title] ${description}`);
1584
+ auditResult = {
1585
+ ...auditResult,
1586
+ issues: [...auditResult.issues, {
1587
+ severity: "warning",
1588
+ category: "title-dedup",
1589
+ description,
1590
+ suggestion: pipelineLang === "en"
1591
+ ? "If the auto-renamed title is weak, revise the chapter title manually."
1592
+ : "如果自动改名不理想,可以在后续手动修订章节标题。",
1593
+ }],
1594
+ };
1595
+ }
1596
+ const longSpanFatigue = await analyzeLongSpanFatigue({
1597
+ bookDir,
1598
+ chapterNumber,
1599
+ chapterContent: finalContent,
1600
+ chapterSummary: persistenceOutput.chapterSummary,
1601
+ language: pipelineLang,
1602
+ });
1603
+ auditResult = {
1604
+ ...auditResult,
1605
+ issues: [
1606
+ ...auditResult.issues,
1607
+ ...longSpanFatigue.issues,
1608
+ ...(persistenceOutput.hookHealthIssues ?? []),
1609
+ ],
1610
+ };
1611
+ finalWordCount = persistenceOutput.wordCount;
1612
+ const lengthWarnings = this.buildLengthWarnings(chapterNumber, finalWordCount, lengthSpec);
1613
+ const lengthTelemetry = this.buildLengthTelemetry({
1614
+ lengthSpec,
1615
+ writerCount,
1616
+ postReviseCount,
1617
+ finalCount: finalWordCount,
1618
+ repairApplied,
1619
+ lengthWarning: lengthWarnings.length > 0,
1620
+ });
1621
+ this.logLengthWarnings(lengthWarnings);
1622
+ // 4.1 Validate settler output before writing
1623
+ this.logStage(stageLanguage, { zh: "校验真相文件变更", en: "validating truth file updates" });
1624
+ const storyDir = join(bookDir, "story");
1625
+ const [oldState, oldHooks, oldLedger, authorityStoryFrame, authorityBookRules, authorityChapterSummaries] = await Promise.all([
1626
+ readFile(join(storyDir, "current_state.md"), "utf-8").catch(() => ""),
1627
+ readFile(join(storyDir, "pending_hooks.md"), "utf-8").catch(() => ""),
1628
+ readFile(join(storyDir, "particle_ledger.md"), "utf-8").catch(() => ""),
1629
+ readStoryFrame(bookDir).catch(() => ""),
1630
+ readFile(join(storyDir, "book_rules.md"), "utf-8").catch(() => ""),
1631
+ readFile(join(storyDir, "chapter_summaries.md"), "utf-8").catch(() => ""),
1632
+ ]);
1633
+ const validator = new StateValidatorAgent(this.agentCtxFor("state-validator", bookId));
1634
+ const truthValidation = await validateChapterTruthPersistence({
1635
+ writer,
1636
+ validator,
1637
+ book,
1638
+ bookDir,
1639
+ chapterNumber,
1640
+ title: persistenceOutput.title,
1641
+ content: finalContent,
1642
+ persistenceOutput,
1643
+ auditResult,
1644
+ previousTruth: {
1645
+ oldState,
1646
+ oldHooks,
1647
+ oldLedger,
1648
+ },
1649
+ authorityContext: {
1650
+ storyFrame: authorityStoryFrame,
1651
+ bookRules: authorityBookRules,
1652
+ chapterSummaries: authorityChapterSummaries,
1653
+ },
1654
+ reducedControlInput,
1655
+ language: pipelineLang,
1656
+ logWarn: (message) => this.logWarn(pipelineLang, message),
1657
+ logger: this.config.logger,
1658
+ });
1659
+ let chapterStatus = truthValidation.chapterStatus;
1660
+ let degradedIssues = truthValidation.degradedIssues;
1661
+ persistenceOutput = truthValidation.persistenceOutput;
1662
+ auditResult = truthValidation.auditResult;
1663
+ // 4.2 Final paragraph shape check on persisted content (post-normalize, post-revise)
1664
+ {
1665
+ const { detectParagraphLengthDrift, detectParagraphShapeWarnings, } = await import("../agents/post-write-validator.js");
1666
+ const chapDir = join(bookDir, "chapters");
1667
+ const recentFiles = (await readdir(chapDir).catch(() => []))
1668
+ .filter((f) => f.endsWith(".md") && /^\d{4}/.test(f))
1669
+ .sort()
1670
+ .slice(-5);
1671
+ const recentContent = (await Promise.all(recentFiles.map((f) => readFile(join(chapDir, f), "utf-8").catch(() => "")))).join("\n\n");
1672
+ const paragraphIssues = [
1673
+ ...detectParagraphShapeWarnings(finalContent, pipelineLang),
1674
+ ...detectParagraphLengthDrift(finalContent, recentContent, pipelineLang),
1675
+ ];
1676
+ if (paragraphIssues.length > 0) {
1677
+ for (const issue of paragraphIssues) {
1678
+ this.config.logger?.warn(`[paragraph] ${issue.description}`);
1679
+ }
1680
+ auditResult = {
1681
+ ...auditResult,
1682
+ issues: [...auditResult.issues, ...paragraphIssues.map((v) => ({
1683
+ severity: v.severity,
1684
+ category: "paragraph-shape",
1685
+ description: v.description,
1686
+ suggestion: v.suggestion,
1687
+ }))],
1688
+ };
1689
+ }
1690
+ }
1691
+ const resolvedStatus = chapterStatus ?? (auditResult.passed ? "ready-for-review" : "audit-failed");
1692
+ await persistChapterArtifacts({
1693
+ chapterNumber,
1694
+ chapterTitle: persistenceOutput.title,
1695
+ status: resolvedStatus,
1696
+ auditResult,
1697
+ finalWordCount,
1698
+ lengthWarnings,
1699
+ lengthTelemetry,
1700
+ degradedIssues,
1701
+ tokenUsage: totalUsage,
1702
+ loadChapterIndex: () => this.state.loadChapterIndex(bookId),
1703
+ saveChapter: () => writer.saveChapter(bookDir, persistenceOutput, gp.numericalSystem, pipelineLang),
1704
+ saveTruthFiles: async () => {
1705
+ await this.syncLegacyStructuredStateFromMarkdown(bookDir, chapterNumber, persistenceOutput);
1706
+ this.logStage(stageLanguage, { zh: "同步记忆索引", en: "syncing memory indexes" });
1707
+ await this.syncNarrativeMemoryIndex(bookId);
1708
+ },
1709
+ saveChapterIndex: (index) => this.state.saveChapterIndex(bookId, index),
1710
+ markBookActiveIfNeeded: () => this.markBookActiveIfNeeded(bookId),
1711
+ persistAuditDriftGuidance: (issues) => this.persistAuditDriftGuidance({
1712
+ bookDir,
1713
+ chapterNumber,
1714
+ issues,
1715
+ language: stageLanguage,
1716
+ }).catch(() => undefined),
1717
+ snapshotState: () => this.state.snapshotState(bookId, chapterNumber),
1718
+ syncCurrentStateFactHistory: () => this.syncCurrentStateFactHistory(bookId, chapterNumber),
1719
+ logSnapshotStage: () => this.logStage(stageLanguage, { zh: "更新章节索引与快照", en: "updating chapter index and snapshots" }),
1720
+ });
1721
+ // 6. Send notification
1722
+ if (this.config.notifyChannels && this.config.notifyChannels.length > 0) {
1723
+ const statusEmoji = resolvedStatus === "state-degraded"
1724
+ ? "🧯"
1725
+ : auditResult.passed ? "✅" : "⚠️";
1726
+ const chapterLength = formatLengthCount(finalWordCount, lengthSpec.countingMode);
1727
+ await dispatchNotification(this.config.notifyChannels, {
1728
+ title: `${statusEmoji} ${book.title} 第${chapterNumber}章`,
1729
+ body: [
1730
+ `**${persistenceOutput.title}** | ${chapterLength}`,
1731
+ revised ? "📝 已自动修正" : "",
1732
+ resolvedStatus === "state-degraded"
1733
+ ? "状态结算: 已降级保存,需先修复 state 再继续"
1734
+ : `审稿: ${auditResult.passed ? "通过" : "需人工审核"}`,
1735
+ ...auditResult.issues
1736
+ .filter((i) => i.severity !== "info")
1737
+ .map((i) => `- [${i.severity}] ${i.description}`),
1738
+ ]
1739
+ .filter(Boolean)
1740
+ .join("\n"),
1741
+ });
1742
+ }
1743
+ await this.emitWebhook("pipeline-complete", bookId, chapterNumber, {
1744
+ title: persistenceOutput.title,
1745
+ wordCount: finalWordCount,
1746
+ passed: auditResult.passed,
1747
+ revised,
1748
+ status: resolvedStatus,
1749
+ });
1750
+ return {
1751
+ chapterNumber,
1752
+ title: persistenceOutput.title,
1753
+ wordCount: finalWordCount,
1754
+ auditResult,
1755
+ revised,
1756
+ status: resolvedStatus,
1757
+ lengthWarnings,
1758
+ lengthTelemetry,
1759
+ tokenUsage: totalUsage,
1760
+ ...(writeInput.contextTrace ? { contextTrace: writeInput.contextTrace } : {}),
1761
+ };
1762
+ }
1763
+ async _repairChapterStateLocked(bookId, chapterNumber) {
1764
+ const book = await this.state.loadBookConfig(bookId);
1765
+ const bookDir = this.state.bookDir(bookId);
1766
+ const stageLanguage = await this.resolveBookLanguage(book);
1767
+ const index = [...(await this.state.loadChapterIndex(bookId))];
1768
+ if (index.length === 0) {
1769
+ throw new Error(`Book "${bookId}" has no persisted chapters to repair.`);
1770
+ }
1771
+ const targetChapter = chapterNumber ?? index[index.length - 1].number;
1772
+ const targetIndex = index.findIndex((chapter) => chapter.number === targetChapter);
1773
+ if (targetIndex < 0) {
1774
+ throw new Error(`Chapter ${targetChapter} not found in "${bookId}".`);
1775
+ }
1776
+ const targetMeta = index[targetIndex];
1777
+ const latestChapter = Math.max(...index.map((chapter) => chapter.number));
1778
+ if (targetMeta.status !== "state-degraded") {
1779
+ throw new Error(`Chapter ${targetChapter} is not state-degraded.`);
1780
+ }
1781
+ if (targetChapter !== latestChapter) {
1782
+ throw new Error(`Only the latest state-degraded chapter can be repaired safely (latest is ${latestChapter}).`);
1783
+ }
1784
+ this.logStage(stageLanguage, { zh: "修复章节状态结算", en: "repairing chapter state settlement" });
1785
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
1786
+ const pipelineLang = book.language ?? gp.language;
1787
+ const content = await this.readChapterContent(bookDir, targetChapter);
1788
+ const baselineChapter = targetChapter - 1;
1789
+ const baselineStoryDir = join(bookDir, "story", "snapshots", String(baselineChapter));
1790
+ const [oldState, oldHooks] = await Promise.all([
1791
+ readFile(join(baselineStoryDir, "current_state.md"), "utf-8"),
1792
+ readFile(join(baselineStoryDir, "pending_hooks.md"), "utf-8"),
1793
+ ]).catch((error) => {
1794
+ throw new Error(`Cannot repair chapter ${targetChapter} safely: baseline snapshot ${baselineChapter} is unavailable (${String(error)})`);
1795
+ });
1796
+ const writer = new WriterAgent(this.agentCtxFor("writer", bookId));
1797
+ let repairedOutput = await writer.settleChapterState({
1798
+ book,
1799
+ bookDir,
1800
+ chapterNumber: targetChapter,
1801
+ baselineChapter,
1802
+ title: targetMeta.title,
1803
+ content,
1804
+ allowReapply: true,
1805
+ });
1806
+ const validator = new StateValidatorAgent(this.agentCtxFor("state-validator", bookId));
1807
+ let validation = await validator.validate(content, targetChapter, oldState, repairedOutput.updatedState, oldHooks, repairedOutput.updatedHooks, pipelineLang);
1808
+ if (!validation.passed) {
1809
+ const recovery = await retrySettlementAfterValidationFailure({
1810
+ writer,
1811
+ validator,
1812
+ book,
1813
+ bookDir,
1814
+ chapterNumber: targetChapter,
1815
+ baselineChapter,
1816
+ title: targetMeta.title,
1817
+ content,
1818
+ oldState,
1819
+ oldHooks,
1820
+ originalValidation: validation,
1821
+ language: pipelineLang,
1822
+ logWarn: (message) => this.logWarn(pipelineLang, message),
1823
+ logger: this.config.logger,
1824
+ });
1825
+ if (recovery.kind !== "recovered") {
1826
+ throw new Error(recovery.issues[0]?.description
1827
+ ?? `State repair still failed for chapter ${targetChapter}.`);
1828
+ }
1829
+ repairedOutput = recovery.output;
1830
+ validation = recovery.validation;
1831
+ }
1832
+ if (!validation.passed) {
1833
+ throw new Error(`State repair still failed for chapter ${targetChapter}.`);
1834
+ }
1835
+ await writer.saveChapter(bookDir, repairedOutput, gp.numericalSystem, pipelineLang);
1836
+ await this.syncLegacyStructuredStateFromMarkdown(bookDir, targetChapter, repairedOutput);
1837
+ await this.syncNarrativeMemoryIndex(bookId);
1838
+ await this.state.snapshotState(bookId, targetChapter);
1839
+ await this.syncCurrentStateFactHistory(bookId, targetChapter);
1840
+ const baseStatus = resolveStateDegradedBaseStatus(targetMeta);
1841
+ const degradedMetadata = parseStateDegradedReviewNote(targetMeta.reviewNote);
1842
+ const injectedIssues = new Set(degradedMetadata?.injectedIssues ?? []);
1843
+ index[targetIndex] = {
1844
+ ...targetMeta,
1845
+ status: baseStatus,
1846
+ updatedAt: new Date().toISOString(),
1847
+ auditIssues: targetMeta.auditIssues.filter((issue) => !injectedIssues.has(issue)),
1848
+ reviewNote: undefined,
1849
+ };
1850
+ await this.state.saveChapterIndex(bookId, index);
1851
+ const repairedPassesAudit = baseStatus !== "audit-failed";
1852
+ return {
1853
+ chapterNumber: targetChapter,
1854
+ title: targetMeta.title,
1855
+ wordCount: targetMeta.wordCount,
1856
+ auditResult: {
1857
+ passed: repairedPassesAudit,
1858
+ issues: [],
1859
+ summary: repairedPassesAudit ? "state repaired" : "state repaired but chapter still needs review",
1860
+ },
1861
+ revised: false,
1862
+ status: baseStatus,
1863
+ lengthWarnings: targetMeta.lengthWarnings,
1864
+ lengthTelemetry: targetMeta.lengthTelemetry,
1865
+ tokenUsage: targetMeta.tokenUsage,
1866
+ };
1867
+ }
1868
+ async _resyncChapterArtifactsLocked(bookId, chapterNumber, options = {}) {
1869
+ const book = await this.state.loadBookConfig(bookId);
1870
+ const bookDir = this.state.bookDir(bookId);
1871
+ const stageLanguage = await this.resolveBookLanguage(book);
1872
+ const index = [...(await this.state.loadChapterIndex(bookId))];
1873
+ if (index.length === 0) {
1874
+ throw new Error(`Book "${bookId}" has no persisted chapters to sync.`);
1875
+ }
1876
+ const targetChapter = chapterNumber ?? index[index.length - 1].number;
1877
+ const targetIndex = index.findIndex((chapter) => chapter.number === targetChapter);
1878
+ if (targetIndex < 0) {
1879
+ throw new Error(`Chapter ${targetChapter} not found in "${bookId}".`);
1880
+ }
1881
+ const targetMeta = index[targetIndex];
1882
+ const latestChapter = Math.max(...index.map((chapter) => chapter.number));
1883
+ if (targetChapter !== latestChapter) {
1884
+ throw new Error(`Only the latest persisted chapter can be synced safely (latest is ${latestChapter}).`);
1885
+ }
1886
+ this.logStage(stageLanguage, { zh: "根据已编辑正文同步真相文件与索引", en: "syncing truth files and indexes from edited chapter body" });
1887
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
1888
+ const pipelineLang = book.language ?? gp.language;
1889
+ const content = await this.readChapterContent(bookDir, targetChapter);
1890
+ const baselineChapter = targetChapter - 1;
1891
+ const baselineStoryDir = join(bookDir, "story", "snapshots", String(baselineChapter));
1892
+ const [oldState, oldHooks] = await Promise.all([
1893
+ readFile(join(baselineStoryDir, "current_state.md"), "utf-8"),
1894
+ readFile(join(baselineStoryDir, "pending_hooks.md"), "utf-8"),
1895
+ ]).catch((error) => {
1896
+ throw new Error(`Cannot sync chapter ${targetChapter} safely: baseline snapshot ${baselineChapter} is unavailable (${String(error)})`);
1897
+ });
1898
+ const reducedControlInput = await this.createGovernedArtifacts(book, bookDir, targetChapter, this.config.externalContext, { reuseExistingIntentWhenContextMissing: true });
1899
+ const writer = new WriterAgent(this.agentCtxFor("writer", bookId));
1900
+ let syncedOutput = await writer.settleChapterState({
1901
+ book,
1902
+ bookDir,
1903
+ chapterNumber: targetChapter,
1904
+ baselineChapter,
1905
+ allowNewHooks: options.allowNewHooks,
1906
+ title: targetMeta.title,
1907
+ content,
1908
+ chapterIntent: reducedControlInput?.plan.intentMarkdown,
1909
+ contextPackage: reducedControlInput?.composed.contextPackage,
1910
+ ruleStack: reducedControlInput?.composed.ruleStack,
1911
+ allowReapply: true,
1912
+ });
1913
+ const validator = new StateValidatorAgent(this.agentCtxFor("state-validator", bookId));
1914
+ let validation = await validator.validate(content, targetChapter, oldState, syncedOutput.updatedState, oldHooks, syncedOutput.updatedHooks, pipelineLang);
1915
+ if (!validation.passed) {
1916
+ const recovery = await retrySettlementAfterValidationFailure({
1917
+ writer,
1918
+ validator,
1919
+ book,
1920
+ bookDir,
1921
+ chapterNumber: targetChapter,
1922
+ baselineChapter,
1923
+ allowNewHooks: options.allowNewHooks,
1924
+ title: targetMeta.title,
1925
+ content,
1926
+ reducedControlInput: reducedControlInput
1927
+ ? {
1928
+ chapterIntent: reducedControlInput.plan.intentMarkdown,
1929
+ contextPackage: reducedControlInput.composed.contextPackage,
1930
+ ruleStack: reducedControlInput.composed.ruleStack,
1931
+ }
1932
+ : undefined,
1933
+ oldState,
1934
+ oldHooks,
1935
+ originalValidation: validation,
1936
+ language: pipelineLang,
1937
+ logWarn: (message) => this.logWarn(pipelineLang, message),
1938
+ logger: this.config.logger,
1939
+ });
1940
+ if (recovery.kind !== "recovered") {
1941
+ throw new Error(recovery.issues[0]?.description
1942
+ ?? `Chapter sync still failed for chapter ${targetChapter}.`);
1943
+ }
1944
+ syncedOutput = recovery.output;
1945
+ validation = recovery.validation;
1946
+ }
1947
+ if (!validation.passed) {
1948
+ throw new Error(`Chapter sync still failed for chapter ${targetChapter}.`);
1949
+ }
1950
+ await writer.saveChapter(bookDir, syncedOutput, gp.numericalSystem, pipelineLang);
1951
+ await this.syncLegacyStructuredStateFromMarkdown(bookDir, targetChapter, syncedOutput);
1952
+ await this.syncNarrativeMemoryIndex(bookId);
1953
+ await this.state.snapshotState(bookId, targetChapter);
1954
+ await this.syncCurrentStateFactHistory(bookId, targetChapter);
1955
+ const finalStatus = targetMeta.status === "state-degraded"
1956
+ ? resolveStateDegradedBaseStatus(targetMeta)
1957
+ : "ready-for-review";
1958
+ if (targetMeta.status === "state-degraded") {
1959
+ const degradedMetadata = parseStateDegradedReviewNote(targetMeta.reviewNote);
1960
+ const injectedIssues = new Set(degradedMetadata?.injectedIssues ?? []);
1961
+ index[targetIndex] = {
1962
+ ...targetMeta,
1963
+ status: finalStatus,
1964
+ updatedAt: new Date().toISOString(),
1965
+ auditIssues: targetMeta.auditIssues.filter((issue) => !injectedIssues.has(issue)),
1966
+ reviewNote: undefined,
1967
+ };
1968
+ }
1969
+ else {
1970
+ index[targetIndex] = {
1971
+ ...targetMeta,
1972
+ status: "ready-for-review",
1973
+ updatedAt: new Date().toISOString(),
1974
+ };
1975
+ }
1976
+ await this.state.saveChapterIndex(bookId, index);
1977
+ return {
1978
+ chapterNumber: targetChapter,
1979
+ title: targetMeta.title,
1980
+ wordCount: targetMeta.wordCount,
1981
+ auditResult: {
1982
+ passed: finalStatus !== "audit-failed",
1983
+ issues: [],
1984
+ summary: finalStatus === "audit-failed"
1985
+ ? "chapter truth/state resynced from edited body, but chapter still needs audit fixes"
1986
+ : "chapter truth/state resynced from edited body",
1987
+ },
1988
+ revised: false,
1989
+ status: finalStatus,
1990
+ lengthWarnings: targetMeta.lengthWarnings,
1991
+ lengthTelemetry: targetMeta.lengthTelemetry,
1992
+ tokenUsage: targetMeta.tokenUsage,
1993
+ };
1994
+ }
1995
+ // ---------------------------------------------------------------------------
1996
+ // Import operations (style imitation + canon for spinoff)
1997
+ // ---------------------------------------------------------------------------
1998
+ /**
1999
+ * Generate a qualitative style guide from reference text via LLM.
2000
+ * Also saves the statistical style_profile.json.
2001
+ */
2002
+ async generateStyleGuide(bookId, referenceText, sourceName) {
2003
+ const sample = referenceText.trim();
2004
+ if (!sample) {
2005
+ throw new Error("Reference text is required for style extraction.");
2006
+ }
2007
+ const { analyzeStyle } = await import("../agents/style-analyzer.js");
2008
+ const bookDir = this.state.bookDir(bookId);
2009
+ const storyDir = join(bookDir, "story");
2010
+ await mkdir(storyDir, { recursive: true });
2011
+ const book = await this.state.loadBookConfig(bookId);
2012
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
2013
+ const lang = (book.language ?? gp.language) === "en" ? "en" : "zh";
2014
+ // Statistical fingerprint (language-aware: words for en, characters for zh)
2015
+ const profile = analyzeStyle(sample, sourceName, lang);
2016
+ await writeFile(join(storyDir, "style_profile.json"), JSON.stringify(profile, null, 2), "utf-8");
2017
+ let qualitativeGuide;
2018
+ if (sample.length < 500) {
2019
+ qualitativeGuide = this.buildDeterministicStyleGuide(profile, {
2020
+ language: lang,
2021
+ reason: lang === "en"
2022
+ ? `The sample is short (${sample.length} chars), so this guide uses the statistical fingerprint instead of LLM qualitative extraction.`
2023
+ : `样本文本较短(${sample.length}字),本次先使用统计指纹生成文风指南,不强行调用 LLM 做定性拆解。`,
2024
+ });
2025
+ }
2026
+ else {
2027
+ try {
2028
+ // LLM qualitative extraction (language-aware prompt)
2029
+ const styleSystemPrompt = lang === "en"
2030
+ ? `You are a literary style analyst. Analyze the writing style of the reference text and extract qualitative, imitable features.
2031
+
2032
+ Output format (Markdown):
2033
+ ## Narrative Voice & Tone
2034
+ (detached / fervent / ironic / warm / ..., with 1-2 quoted lines from the text)
2035
+
2036
+ ## Dialogue Style
2037
+ (shared traits in how characters speak: sentence length, verbal tics, dialect markers, dialogue rhythm)
2038
+
2039
+ ## Scene Description
2040
+ (sensory preferences, choice of imagery, description density, how setting ties to emotion)
2041
+
2042
+ ## Transitions & Connective Technique
2043
+ (how scenes switch, how time jumps are handled, paragraph-to-paragraph transitions)
2044
+
2045
+ ## Pacing
2046
+ (distribution of long vs short sentences, paragraph-length preference, how climaxes and lulls alternate)
2047
+
2048
+ ## Diction
2049
+ (signature high-frequency word choices, figurative/rhetorical tendencies, degree of colloquialism)
2050
+
2051
+ ## Emotional Expression
2052
+ (direct lyricism vs externalized action, frequency and style of interior monologue)
2053
+
2054
+ ## Distinctive Habits
2055
+ (any personal writing habits worth imitating)
2056
+
2057
+ Base the analysis on the text's actual features, not generalities. Support each section with 1-2 quoted lines from the original.`
2058
+ : `你是一位文学风格分析专家。分析参考文本的写作风格,提取可供模仿的定性特征。
2059
+
2060
+ 输出格式(Markdown):
2061
+ ## 叙事声音与语气
2062
+ (冷峻/热烈/讽刺/温情/...,附1-2个原文例句)
2063
+
2064
+ ## 对话风格
2065
+ (角色说话的共性特征:句子长短、口头禅倾向、方言痕迹、对话节奏)
2066
+
2067
+ ## 场景描写特征
2068
+ (五感偏好、意象选择、描写密度、环境与情绪的关联方式)
2069
+
2070
+ ## 转折与衔接手法
2071
+ (场景如何切换、时间跳跃的处理方式、段落间的过渡特征)
2072
+
2073
+ ## 节奏特征
2074
+ (长短句分布、段落长度偏好、高潮/舒缓的交替方式)
2075
+
2076
+ ## 词汇偏好
2077
+ (高频特色用词、比喻/修辞倾向、口语化程度)
2078
+
2079
+ ## 情绪表达方式
2080
+ (直白抒情 vs 动作外化、内心独白的频率和风格)
2081
+
2082
+ ## 独特习惯
2083
+ (任何值得模仿的个人写作习惯)
2084
+
2085
+ 分析必须基于原文实际特征,不要泛泛而谈。每个部分用1-2个原文例句佐证。`;
2086
+ const styleUserPrompt = lang === "en"
2087
+ ? `Analyze the writing style of the following reference text:\n\n${sample}`
2088
+ : `分析以下参考文本的写作风格:\n\n${sample}`;
2089
+ const response = await runWorkerAgent(this.config.client, this.config.model, appendActivatedSkillGuidance([
2090
+ { role: "system", content: styleSystemPrompt },
2091
+ { role: "user", content: styleUserPrompt },
2092
+ ], this.currentActivatedSkills()), { temperature: 0.3, signal: this.currentAbortSignal() });
2093
+ qualitativeGuide = response.content.trim()
2094
+ ? response.content
2095
+ : this.buildDeterministicStyleGuide(profile, {
2096
+ language: lang,
2097
+ reason: lang === "en"
2098
+ ? "The LLM returned empty style analysis; using the statistical fingerprint fallback."
2099
+ : "LLM 未返回有效文风分析,本次使用统计指纹兜底生成文风指南。",
2100
+ });
2101
+ }
2102
+ catch (error) {
2103
+ qualitativeGuide = this.buildDeterministicStyleGuide(profile, {
2104
+ language: lang,
2105
+ reason: lang === "en"
2106
+ ? `LLM qualitative extraction failed: ${error instanceof Error ? error.message : String(error)}. Using the statistical fingerprint fallback.`
2107
+ : `LLM 定性拆解失败:${error instanceof Error ? error.message : String(error)}。本次使用统计指纹兜底生成文风指南。`,
2108
+ });
2109
+ }
2110
+ }
2111
+ const craftMethodology = buildWritingMethodologySection(lang);
2112
+ const fullStyleGuide = `${qualitativeGuide}\n\n${craftMethodology}`;
2113
+ await writeFile(join(storyDir, "style_guide.md"), fullStyleGuide, "utf-8");
2114
+ return fullStyleGuide;
2115
+ }
2116
+ buildDeterministicStyleGuide(profile, options) {
2117
+ if (options.language === "en") {
2118
+ return [
2119
+ "# Style Guide",
2120
+ "",
2121
+ `> ${options.reason}`,
2122
+ "",
2123
+ "## Statistical Fingerprint",
2124
+ `- Source: ${profile.sourceName ?? "unknown"}`,
2125
+ `- Average sentence length: ${profile.avgSentenceLength}`,
2126
+ `- Sentence length variance: ${profile.sentenceLengthStdDev}`,
2127
+ `- Average paragraph length: ${profile.avgParagraphLength}`,
2128
+ `- Vocabulary diversity: ${Math.round(profile.vocabularyDiversity * 100)}%`,
2129
+ profile.topPatterns.length > 0 ? `- Repeated openings: ${profile.topPatterns.join(", ")}` : "- Repeated openings: none obvious in this sample",
2130
+ profile.rhetoricalFeatures.length > 0 ? `- Rhetorical features: ${profile.rhetoricalFeatures.join(", ")}` : "- Rhetorical features: none obvious in this sample",
2131
+ "",
2132
+ "## How To Use",
2133
+ "- Treat this as a lightweight style fingerprint, not a full imitation bible.",
2134
+ "- Keep sentence and paragraph rhythm close to the sample when drafting.",
2135
+ "- If this guide feels too thin, import a longer excerpt later; the file will be replaced.",
2136
+ ].join("\n");
2137
+ }
2138
+ return [
2139
+ "# 文风指南",
2140
+ "",
2141
+ `> ${options.reason}`,
2142
+ "",
2143
+ "## 统计风格指纹",
2144
+ `- 来源:${profile.sourceName ?? "unknown"}`,
2145
+ `- 平均句长:${profile.avgSentenceLength}`,
2146
+ `- 句长波动:${profile.sentenceLengthStdDev}`,
2147
+ `- 平均段落长度:${profile.avgParagraphLength}`,
2148
+ `- 词汇多样性:${Math.round(profile.vocabularyDiversity * 100)}%`,
2149
+ profile.topPatterns.length > 0 ? `- 高频句首/模式:${profile.topPatterns.join("、")}` : "- 高频句首/模式:样本内不明显",
2150
+ profile.rhetoricalFeatures.length > 0 ? `- 修辞特征:${profile.rhetoricalFeatures.join("、")}` : "- 修辞特征:样本内不明显",
2151
+ "",
2152
+ "## 使用方式",
2153
+ "- 这是一份轻量文风指纹,不是完整仿写圣经。",
2154
+ "- 后续写作优先参考句长、段落长度、节奏波动和可见修辞。",
2155
+ "- 如果想得到更稳定的定性拆解,后续可以导入更长片段覆盖本文件。",
2156
+ ].join("\n");
2157
+ }
2158
+ /**
2159
+ * Import canon from parent book for spinoff writing.
2160
+ * Reads parent's truth files, uses LLM to generate parent_canon.md in target book.
2161
+ */
2162
+ async importCanon(targetBookId, parentBookId) {
2163
+ // Validate both books exist
2164
+ const bookIds = await this.state.listBooks();
2165
+ if (!bookIds.includes(parentBookId)) {
2166
+ throw new Error(`Parent book "${parentBookId}" not found. Available: ${bookIds.join(", ") || "(none)"}`);
2167
+ }
2168
+ if (!bookIds.includes(targetBookId)) {
2169
+ throw new Error(`Target book "${targetBookId}" not found. Available: ${bookIds.join(", ") || "(none)"}`);
2170
+ }
2171
+ const parentDir = this.state.bookDir(parentBookId);
2172
+ const targetDir = this.state.bookDir(targetBookId);
2173
+ const storyDir = join(targetDir, "story");
2174
+ await mkdir(storyDir, { recursive: true });
2175
+ const readSafe = async (path) => {
2176
+ try {
2177
+ return await readFile(path, "utf-8");
2178
+ }
2179
+ catch {
2180
+ return "(无)";
2181
+ }
2182
+ };
2183
+ const parentBook = await this.state.loadBookConfig(parentBookId);
2184
+ // Phase 5: parent book may be on the new prose layout; prefer outline/.
2185
+ const readParentOutline = async (newRel, legacyRel) => {
2186
+ const preferred = await readSafe(join(parentDir, "story", newRel));
2187
+ if (preferred.trim() && preferred !== "(无)")
2188
+ return preferred;
2189
+ return readSafe(join(parentDir, "story", legacyRel));
2190
+ };
2191
+ const [storyBible, currentState, ledger, hooks, summaries, subplots, emotions, matrix] = await Promise.all([
2192
+ readParentOutline("outline/story_frame.md", "story_bible.md"),
2193
+ readSafe(join(parentDir, "story/current_state.md")),
2194
+ readSafe(join(parentDir, "story/particle_ledger.md")),
2195
+ readSafe(join(parentDir, "story/pending_hooks.md")),
2196
+ readSafe(join(parentDir, "story/chapter_summaries.md")),
2197
+ readSafe(join(parentDir, "story/subplot_board.md")),
2198
+ readSafe(join(parentDir, "story/emotional_arcs.md")),
2199
+ readSafe(join(parentDir, "story/character_matrix.md")),
2200
+ ]);
2201
+ const response = await runWorkerAgent(this.config.client, this.config.model, appendActivatedSkillGuidance([
2202
+ {
2203
+ role: "system",
2204
+ content: `你是一位网络小说架构师。基于正传的全部设定和状态文件,生成一份完整的"正传正典参照"文档,供番外写作和审计使用。
2205
+
2206
+ 输出格式(Markdown):
2207
+ # 正传正典(《{正传书名}》)
2208
+
2209
+ ## 世界规则(完整,来自正传设定)
2210
+ (力量体系、地理设定、阵营关系、核心规则——完整复制,不压缩)
2211
+
2212
+ ## 正典约束(不可违反的事实)
2213
+ | 约束ID | 类型 | 约束内容 | 严重性 |
2214
+ |---|---|---|---|
2215
+ | C01 | 人物存亡 | ... | critical |
2216
+ (列出所有硬性约束:谁活着、谁死了、什么事件已经发生、什么规则不可违反)
2217
+
2218
+ ## 角色快照
2219
+ | 角色 | 当前状态 | 性格底色 | 对话特征 | 已知信息 | 未知信息 |
2220
+ |---|---|---|---|---|---|
2221
+ (从状态卡和角色矩阵中提取每个重要角色的完整快照)
2222
+
2223
+ ## 角色双态处理原则
2224
+ - 未来会变强的角色:写潜力暗示
2225
+ - 未来会黑化的角色:写微小裂痕
2226
+ - 未来会死的角色:写导致死亡的性格底色
2227
+
2228
+ ## 关键事件时间线
2229
+ | 章节 | 事件 | 涉及角色 | 对番外的约束 |
2230
+ |---|---|---|---|
2231
+ (从章节摘要中提取关键事件)
2232
+
2233
+ ## 伏笔状态
2234
+ | Hook ID | 类型 | 状态 | 内容 | 预期回收 |
2235
+ |---|---|---|---|---|
2236
+
2237
+ ## 资源账本快照
2238
+ (当前资源状态)
2239
+
2240
+ ---
2241
+ meta:
2242
+ parentBookId: "{parentBookId}"
2243
+ parentTitle: "{正传书名}"
2244
+ generatedAt: "{ISO timestamp}"
2245
+
2246
+ 要求:
2247
+ 1. 世界规则完整复制,不压缩——准确性优先
2248
+ 2. 正典约束必须穷尽,遗漏会导致番外与正传矛盾
2249
+ 3. 角色快照必须包含信息边界(已知/未知),防止番外中角色引用不该知道的信息`,
2250
+ },
2251
+ {
2252
+ role: "user",
2253
+ content: `正传书名:${parentBook.title}
2254
+ 正传ID:${parentBookId}
2255
+
2256
+ ## 正传世界设定
2257
+ ${storyBible}
2258
+
2259
+ ## 正传当前状态卡
2260
+ ${currentState}
2261
+
2262
+ ## 正传资源账本
2263
+ ${ledger}
2264
+
2265
+ ## 正传伏笔池
2266
+ ${hooks}
2267
+
2268
+ ## 正传章节摘要
2269
+ ${summaries}
2270
+
2271
+ ## 正传支线进度
2272
+ ${subplots}
2273
+
2274
+ ## 正传情感弧线
2275
+ ${emotions}
2276
+
2277
+ ## 正传角色矩阵
2278
+ ${matrix}`,
2279
+ },
2280
+ ], this.currentActivatedSkills()), { temperature: 0.3, signal: this.currentAbortSignal() });
2281
+ // Append deterministic meta block (LLM may hallucinate timestamps)
2282
+ const metaBlock = [
2283
+ "",
2284
+ "---",
2285
+ "meta:",
2286
+ ` parentBookId: "${parentBookId}"`,
2287
+ ` parentTitle: "${parentBook.title}"`,
2288
+ ` generatedAt: "${new Date().toISOString()}"`,
2289
+ ].join("\n");
2290
+ const canon = response.content + metaBlock;
2291
+ await writeFile(join(storyDir, "parent_canon.md"), canon, "utf-8");
2292
+ // Also generate style guide from parent's chapter text if available
2293
+ const parentChaptersDir = join(parentDir, "chapters");
2294
+ const parentChapterText = await this.readParentChapterSample(parentChaptersDir);
2295
+ if (parentChapterText.length >= 500) {
2296
+ await this.tryGenerateStyleGuide(targetBookId, parentChapterText, parentBook.title);
2297
+ }
2298
+ return canon;
2299
+ }
2300
+ async readParentChapterSample(chaptersDir) {
2301
+ try {
2302
+ const entries = await readdir(chaptersDir);
2303
+ const mdFiles = entries
2304
+ .filter((file) => file.endsWith(".md"))
2305
+ .sort()
2306
+ .slice(0, 5);
2307
+ const chunks = [];
2308
+ let totalLength = 0;
2309
+ for (const file of mdFiles) {
2310
+ if (totalLength >= 20000)
2311
+ break;
2312
+ const content = await readFile(join(chaptersDir, file), "utf-8");
2313
+ chunks.push(content);
2314
+ totalLength += content.length;
2315
+ }
2316
+ return chunks.join("\n\n---\n\n");
2317
+ }
2318
+ catch {
2319
+ return "";
2320
+ }
2321
+ }
2322
+ // ---------------------------------------------------------------------------
2323
+ // Chapter import (for continuation writing from existing chapters)
2324
+ // ---------------------------------------------------------------------------
2325
+ /**
2326
+ * Import existing chapters into a book. Reverse-engineers all truth files
2327
+ * via sequential replay so the Writer and Auditor can continue naturally.
2328
+ *
2329
+ * Step 1: Generate foundation (story_frame, volume_map, book_rules) from all chapters.
2330
+ * Step 2: Sequentially replay each chapter through ChapterAnalyzer to build truth files.
2331
+ */
2332
+ async importChapters(input) {
2333
+ this.throwIfOperationAborted();
2334
+ const releaseLock = await this.state.acquireBookLock(input.bookId);
2335
+ try {
2336
+ const book = await this.state.loadBookConfig(input.bookId);
2337
+ const bookDir = this.state.bookDir(input.bookId);
2338
+ const { profile: gp } = await this.loadGenreProfile(book.genre);
2339
+ const resolvedLanguage = book.language ?? gp.language;
2340
+ const startFrom = input.resumeFrom ?? 1;
2341
+ const log = this.config.logger?.child("import");
2342
+ // Step 1: Generate foundation on first run (not on resume)
2343
+ if (startFrom === 1) {
2344
+ log?.info(this.localize(resolvedLanguage, {
2345
+ zh: `步骤 1:从 ${input.chapters.length} 章生成基础设定...`,
2346
+ en: `Step 1: Generating foundation from ${input.chapters.length} chapters...`,
2347
+ }));
2348
+ const foundationSource = buildImportFoundationSource(input.chapters, resolvedLanguage);
2349
+ const architect = new ArchitectAgent(this.agentCtxFor("architect", input.bookId));
2350
+ const isSeries = input.importMode === "series";
2351
+ const foundation = isSeries
2352
+ ? await this.generateAndReviewFoundation({
2353
+ generate: (reviewFeedback) => architect.generateFoundationFromImport(book, foundationSource, undefined, reviewFeedback, { importMode: "series" }),
2354
+ reviewer: new FoundationReviewerAgent(this.agentCtxFor("foundation-reviewer", input.bookId)),
2355
+ mode: "series",
2356
+ language: resolvedLanguage === "en" ? "en" : "zh",
2357
+ stageLanguage: resolvedLanguage,
2358
+ targetChapters: book.targetChapters,
2359
+ })
2360
+ : await architect.generateFoundationFromImport(book, foundationSource);
2361
+ this.throwIfOperationAborted();
2362
+ await architect.writeFoundationFiles(bookDir, foundation, gp.numericalSystem, resolvedLanguage);
2363
+ await this.resetImportReplayTruthFiles(bookDir, resolvedLanguage);
2364
+ await this.state.saveChapterIndex(input.bookId, [], { allowEmptyWithChapterFiles: true });
2365
+ await this.state.snapshotState(input.bookId, 0);
2366
+ // Generate style guide from imported chapters
2367
+ if (foundationSource.length >= 500) {
2368
+ log?.info(this.localize(resolvedLanguage, {
2369
+ zh: "提取原文风格指纹...",
2370
+ en: "Extracting source style fingerprint...",
2371
+ }));
2372
+ await this.tryGenerateStyleGuide(input.bookId, foundationSource, book.title, resolvedLanguage);
2373
+ }
2374
+ log?.info(this.localize(resolvedLanguage, {
2375
+ zh: "基础设定已生成。",
2376
+ en: "Foundation generated.",
2377
+ }));
2378
+ }
2379
+ // Step 2: Sequential replay
2380
+ log?.info(this.localize(resolvedLanguage, {
2381
+ zh: `步骤 2:从第 ${startFrom} 章开始顺序回放...`,
2382
+ en: `Step 2: Sequential replay from chapter ${startFrom}...`,
2383
+ }));
2384
+ const analyzer = new ChapterAnalyzerAgent(this.agentCtxFor("chapter-analyzer", input.bookId));
2385
+ const writer = new WriterAgent(this.agentCtxFor("writer", input.bookId));
2386
+ const countingMode = resolveLengthCountingMode(book.language ?? gp.language);
2387
+ let totalWords = 0;
2388
+ let importedCount = 0;
2389
+ for (let i = startFrom - 1; i < input.chapters.length; i++) {
2390
+ this.throwIfOperationAborted();
2391
+ const ch = input.chapters[i];
2392
+ const chapterNumber = i + 1;
2393
+ const governedInput = await this.prepareWriteInput(book, bookDir, chapterNumber);
2394
+ log?.info(this.localize(resolvedLanguage, {
2395
+ zh: `分析章节 ${chapterNumber}/${input.chapters.length}:${ch.title}...`,
2396
+ en: `Analyzing chapter ${chapterNumber}/${input.chapters.length}: ${ch.title}...`,
2397
+ }));
2398
+ // Analyze chapter to get truth file updates
2399
+ const output = await analyzer.analyzeChapter({
2400
+ book,
2401
+ bookDir,
2402
+ chapterNumber,
2403
+ chapterContent: ch.content,
2404
+ chapterTitle: ch.title,
2405
+ chapterIntent: governedInput.chapterIntent,
2406
+ contextPackage: governedInput.contextPackage,
2407
+ ruleStack: governedInput.ruleStack,
2408
+ });
2409
+ this.throwIfOperationAborted();
2410
+ const chapterWordCount = countChapterLength(ch.content, countingMode);
2411
+ const persistedOutput = {
2412
+ ...output,
2413
+ content: ch.content,
2414
+ wordCount: chapterWordCount,
2415
+ postWriteErrors: [],
2416
+ postWriteWarnings: [],
2417
+ };
2418
+ // Save chapter file + core truth files (state, ledger, hooks)
2419
+ await writer.saveChapter(bookDir, persistedOutput, gp.numericalSystem, resolvedLanguage);
2420
+ await this.syncLegacyStructuredStateFromMarkdown(bookDir, chapterNumber, output);
2421
+ await this.syncNarrativeMemoryIndex(input.bookId);
2422
+ // Update chapter index
2423
+ const existingIndex = await this.state.loadChapterIndex(input.bookId);
2424
+ const now = new Date().toISOString();
2425
+ const newEntry = {
2426
+ number: chapterNumber,
2427
+ title: output.title,
2428
+ status: "imported",
2429
+ wordCount: chapterWordCount,
2430
+ createdAt: now,
2431
+ updatedAt: now,
2432
+ auditIssues: [],
2433
+ lengthWarnings: [],
2434
+ };
2435
+ // Replace if exists (resume case), otherwise append
2436
+ const existingIdx = existingIndex.findIndex((e) => e.number === chapterNumber);
2437
+ const updatedIndex = existingIdx >= 0
2438
+ ? existingIndex.map((e, idx) => idx === existingIdx ? newEntry : e)
2439
+ : [...existingIndex, newEntry];
2440
+ await this.state.saveChapterIndex(input.bookId, updatedIndex);
2441
+ // Snapshot state after each chapter for rollback + resume support
2442
+ await this.state.snapshotState(input.bookId, chapterNumber);
2443
+ importedCount++;
2444
+ totalWords += chapterWordCount;
2445
+ }
2446
+ if (input.chapters.length > 0) {
2447
+ await this.markBookActiveIfNeeded(input.bookId);
2448
+ await this.syncCurrentStateFactHistory(input.bookId, input.chapters.length);
2449
+ }
2450
+ const nextChapter = input.chapters.length + 1;
2451
+ log?.info(this.localize(resolvedLanguage, {
2452
+ zh: `完成。已导入 ${importedCount} 章,共 ${formatLengthCount(totalWords, countingMode)}。下一章:${nextChapter}`,
2453
+ en: `Done. ${importedCount} chapters imported, ${formatLengthCount(totalWords, countingMode)}. Next chapter: ${nextChapter}`,
2454
+ }));
2455
+ return {
2456
+ bookId: input.bookId,
2457
+ importedCount,
2458
+ totalWords,
2459
+ nextChapter,
2460
+ };
2461
+ }
2462
+ finally {
2463
+ await releaseLock();
2464
+ }
2465
+ }
2466
+ static addUsage(a, b) {
2467
+ if (!b)
2468
+ return a;
2469
+ return {
2470
+ promptTokens: a.promptTokens + b.promptTokens,
2471
+ completionTokens: a.completionTokens + b.completionTokens,
2472
+ totalTokens: a.totalTokens + b.totalTokens,
2473
+ };
2474
+ }
2475
+ async buildPersistenceOutput(bookId, book, bookDir, chapterNumber, output, finalContent, countingMode, reducedControlInput) {
2476
+ if (finalContent === output.content) {
2477
+ return output;
2478
+ }
2479
+ const analyzer = new ChapterAnalyzerAgent(this.agentCtxFor("chapter-analyzer", bookId));
2480
+ const analyzed = await analyzer.analyzeChapter({
2481
+ book,
2482
+ bookDir,
2483
+ chapterNumber,
2484
+ chapterContent: finalContent,
2485
+ chapterTitle: output.title,
2486
+ chapterIntent: reducedControlInput?.chapterIntent,
2487
+ contextPackage: reducedControlInput?.contextPackage,
2488
+ ruleStack: reducedControlInput?.ruleStack,
2489
+ });
2490
+ return {
2491
+ ...analyzed,
2492
+ content: finalContent,
2493
+ wordCount: countChapterLength(finalContent, countingMode),
2494
+ postWriteErrors: [],
2495
+ postWriteWarnings: [],
2496
+ hookHealthIssues: output.hookHealthIssues,
2497
+ tokenUsage: output.tokenUsage,
2498
+ };
2499
+ }
2500
+ async assertNoPendingStateRepair(bookId) {
2501
+ const existingIndex = await this.state.loadChapterIndex(bookId);
2502
+ const latestChapter = [...existingIndex].sort((left, right) => right.number - left.number)[0];
2503
+ if (latestChapter?.status !== "state-degraded") {
2504
+ return;
2505
+ }
2506
+ throw new Error(`Latest chapter ${latestChapter.number} is state-degraded. Repair state or rewrite that chapter before continuing.`);
2507
+ }
2508
+ // ---------------------------------------------------------------------------
2509
+ // Helpers
2510
+ // ---------------------------------------------------------------------------
2511
+ async prepareWriteInput(book, bookDir, chapterNumber, externalContext) {
2512
+ const { plan, composed } = await this.createGovernedArtifacts(book, bookDir, chapterNumber, externalContext, { reuseExistingIntentWhenContextMissing: true });
2513
+ return {
2514
+ externalContext,
2515
+ chapterIntent: plan.intentMarkdown,
2516
+ chapterMemo: plan.memo,
2517
+ chapterIntentData: plan.intent,
2518
+ contextPackage: composed.contextPackage,
2519
+ ruleStack: composed.ruleStack,
2520
+ contextTrace: {
2521
+ tracePath: relativeToBookDir(bookDir, composed.tracePath),
2522
+ selectedSources: [...composed.trace.selectedSources],
2523
+ protectedSources: [...composed.trace.contextTiers.protectedSources],
2524
+ compressibleSources: [...composed.trace.contextTiers.compressibleSources],
2525
+ tokenBudget: { ...composed.trace.tokenBudget },
2526
+ ...(composed.trace.retrieval ? {
2527
+ retrieval: {
2528
+ ...composed.trace.retrieval,
2529
+ candidates: composed.trace.retrieval.candidates.map((candidate) => ({ ...candidate })),
2530
+ ...(composed.trace.retrieval.semanticSelectedIds
2531
+ ? { semanticSelectedIds: [...composed.trace.retrieval.semanticSelectedIds] }
2532
+ : {}),
2533
+ },
2534
+ } : {}),
2535
+ ...(composed.trace.compression ? {
2536
+ compression: {
2537
+ ...composed.trace.compression,
2538
+ protectedSources: [...composed.trace.compression.protectedSources],
2539
+ compressedSources: [...composed.trace.compression.compressedSources],
2540
+ },
2541
+ } : {}),
2542
+ },
2543
+ };
2544
+ }
2545
+ async resetImportReplayTruthFiles(bookDir, language) {
2546
+ const storyDir = join(bookDir, "story");
2547
+ await Promise.all([
2548
+ writeFile(join(storyDir, "current_state.md"), this.buildImportReplayStateSeed(language), "utf-8"),
2549
+ writeFile(join(storyDir, "pending_hooks.md"), this.buildImportReplayHooksSeed(language), "utf-8"),
2550
+ rm(join(storyDir, "chapter_summaries.md"), { force: true }),
2551
+ rm(join(storyDir, "subplot_board.md"), { force: true }),
2552
+ rm(join(storyDir, "emotional_arcs.md"), { force: true }),
2553
+ rm(join(storyDir, "character_matrix.md"), { force: true }),
2554
+ rm(join(storyDir, "volume_summaries.md"), { force: true }),
2555
+ rm(join(storyDir, "particle_ledger.md"), { force: true }),
2556
+ rm(join(storyDir, "memory.db"), { force: true }),
2557
+ rm(join(storyDir, "memory.db-shm"), { force: true }),
2558
+ rm(join(storyDir, "memory.db-wal"), { force: true }),
2559
+ rm(join(storyDir, "state"), { recursive: true, force: true }),
2560
+ rm(join(storyDir, "snapshots"), { recursive: true, force: true }),
2561
+ ]);
2562
+ }
2563
+ buildImportReplayStateSeed(language) {
2564
+ if (language === "en") {
2565
+ return [
2566
+ "# Current State",
2567
+ "",
2568
+ "| Field | Value |",
2569
+ "| --- | --- |",
2570
+ "| Current Chapter | 0 |",
2571
+ "| Current Location | (not set) |",
2572
+ "| Protagonist State | (not set) |",
2573
+ "| Current Goal | (not set) |",
2574
+ "| Current Constraint | (not set) |",
2575
+ "| Current Alliances | (not set) |",
2576
+ "| Current Conflict | (not set) |",
2577
+ "",
2578
+ ].join("\n");
2579
+ }
2580
+ return [
2581
+ "# 当前状态",
2582
+ "",
2583
+ "| 字段 | 值 |",
2584
+ "| --- | --- |",
2585
+ "| 当前章节 | 0 |",
2586
+ "| 当前位置 | (未设定) |",
2587
+ "| 主角状态 | (未设定) |",
2588
+ "| 当前目标 | (未设定) |",
2589
+ "| 当前限制 | (未设定) |",
2590
+ "| 当前敌我 | (未设定) |",
2591
+ "| 当前冲突 | (未设定) |",
2592
+ "",
2593
+ ].join("\n");
2594
+ }
2595
+ buildImportReplayHooksSeed(language) {
2596
+ if (language === "en") {
2597
+ return [
2598
+ "# Pending Hooks",
2599
+ "",
2600
+ "| hook_id | start_chapter | type | status | last_advanced_chapter | expected_payoff | notes |",
2601
+ "| --- | --- | --- | --- | --- | --- | --- |",
2602
+ "",
2603
+ ].join("\n");
2604
+ }
2605
+ return [
2606
+ "# 伏笔池",
2607
+ "",
2608
+ "| hook_id | 起始章节 | 类型 | 状态 | 最近推进 | 预期回收 | 备注 |",
2609
+ "| --- | --- | --- | --- | --- | --- | --- |",
2610
+ "",
2611
+ ].join("\n");
2612
+ }
2613
+ assertChapterContentNotEmpty(content, chapterNumber, stage) {
2614
+ if (content.trim().length > 0)
2615
+ return;
2616
+ throw new Error(`Chapter ${chapterNumber} has empty chapter content after ${stage}`);
2617
+ }
2618
+ async syncCurrentStateFactHistory(bookId, uptoChapter) {
2619
+ const bookDir = this.state.bookDir(bookId);
2620
+ try {
2621
+ await this.rebuildCurrentStateFactHistory(bookDir, uptoChapter);
2622
+ }
2623
+ catch (error) {
2624
+ this.logWarn(await this.resolveBookLanguageById(bookId), {
2625
+ zh: `状态事实同步已跳过:${String(error)}`,
2626
+ en: `State fact sync skipped: ${String(error)}`,
2627
+ });
2628
+ }
2629
+ }
2630
+ async syncLegacyStructuredStateFromMarkdown(bookDir, chapterNumber, output) {
2631
+ if (output?.runtimeStateDelta || output?.runtimeStateSnapshot) {
2632
+ return;
2633
+ }
2634
+ await rewriteStructuredStateFromMarkdown({
2635
+ bookDir,
2636
+ fallbackChapter: chapterNumber,
2637
+ });
2638
+ }
2639
+ async syncNarrativeMemoryIndex(bookId) {
2640
+ const bookDir = this.state.bookDir(bookId);
2641
+ try {
2642
+ await this.rebuildNarrativeMemoryIndex(bookDir);
2643
+ }
2644
+ catch (error) {
2645
+ this.logWarn(await this.resolveBookLanguageById(bookId), {
2646
+ zh: `叙事记忆同步已跳过:${String(error)}`,
2647
+ en: `Narrative memory sync skipped: ${String(error)}`,
2648
+ });
2649
+ }
2650
+ }
2651
+ async rebuildCurrentStateFactHistory(bookDir, uptoChapter) {
2652
+ const memoryDb = await this.withMemoryIndexRetry(async () => {
2653
+ const db = new MemoryDB(bookDir);
2654
+ try {
2655
+ db.resetFacts();
2656
+ const activeFacts = new Map();
2657
+ for (let chapter = 0; chapter <= uptoChapter; chapter++) {
2658
+ const snapshotFacts = await loadSnapshotCurrentStateFacts(bookDir, chapter);
2659
+ if (snapshotFacts.length === 0)
2660
+ continue;
2661
+ const nextFacts = new Map();
2662
+ for (const fact of snapshotFacts) {
2663
+ nextFacts.set(this.factKey(fact), {
2664
+ subject: fact.subject,
2665
+ predicate: fact.predicate,
2666
+ object: fact.object,
2667
+ validFromChapter: chapter,
2668
+ validUntilChapter: null,
2669
+ sourceChapter: chapter,
2670
+ });
2671
+ }
2672
+ for (const [key, previous] of activeFacts.entries()) {
2673
+ const next = nextFacts.get(key);
2674
+ if (!next || next.object !== previous.object) {
2675
+ db.invalidateFact(previous.id, chapter);
2676
+ activeFacts.delete(key);
2677
+ }
2678
+ }
2679
+ for (const [key, fact] of nextFacts.entries()) {
2680
+ if (activeFacts.has(key))
2681
+ continue;
2682
+ const id = db.addFact(fact);
2683
+ activeFacts.set(key, { id, object: fact.object });
2684
+ }
2685
+ }
2686
+ return db;
2687
+ }
2688
+ catch (error) {
2689
+ db.close();
2690
+ throw error;
2691
+ }
2692
+ });
2693
+ try {
2694
+ // No-op: keep the db open only for the duration of the rebuild.
2695
+ }
2696
+ finally {
2697
+ memoryDb.close();
2698
+ }
2699
+ }
2700
+ async rebuildNarrativeMemoryIndex(bookDir) {
2701
+ const memorySeed = await loadNarrativeMemorySeed(bookDir);
2702
+ const memoryDb = await this.withMemoryIndexRetry(() => {
2703
+ const db = new MemoryDB(bookDir);
2704
+ try {
2705
+ db.replaceSummaries(memorySeed.summaries);
2706
+ db.replaceHooks(memorySeed.hooks);
2707
+ return db;
2708
+ }
2709
+ catch (error) {
2710
+ db.close();
2711
+ throw error;
2712
+ }
2713
+ });
2714
+ try {
2715
+ // No-op: keep the db open only for the duration of the rebuild.
2716
+ }
2717
+ finally {
2718
+ memoryDb.close();
2719
+ }
2720
+ }
2721
+ async withMemoryIndexRetry(operation) {
2722
+ const retryDelaysMs = [0, 25, 75];
2723
+ let lastError;
2724
+ for (let attempt = 0; attempt < retryDelaysMs.length; attempt += 1) {
2725
+ try {
2726
+ return await operation();
2727
+ }
2728
+ catch (error) {
2729
+ lastError = error;
2730
+ if (!this.isMemoryIndexBusyError(error) || attempt === retryDelaysMs.length - 1) {
2731
+ throw error;
2732
+ }
2733
+ await new Promise((resolve) => setTimeout(resolve, retryDelaysMs[attempt + 1]));
2734
+ }
2735
+ }
2736
+ throw lastError;
2737
+ }
2738
+ isMemoryIndexBusyError(error) {
2739
+ if (!error)
2740
+ return false;
2741
+ const code = typeof error === "object" && error !== null && "code" in error
2742
+ ? String(error.code ?? "")
2743
+ : "";
2744
+ const message = error instanceof Error
2745
+ ? error.message
2746
+ : String(error);
2747
+ return code === "SQLITE_BUSY"
2748
+ || code === "SQLITE_LOCKED"
2749
+ || /\bSQLITE_BUSY\b/i.test(message)
2750
+ || /\bSQLITE_LOCKED\b/i.test(message)
2751
+ || /database is locked/i.test(message)
2752
+ || /database is busy/i.test(message);
2753
+ }
2754
+ factKey(fact) {
2755
+ return `${fact.subject}::${fact.predicate}`;
2756
+ }
2757
+ buildLengthWarnings(chapterNumber, finalCount, lengthSpec) {
2758
+ if (!isOutsideHardRange(finalCount, lengthSpec)) {
2759
+ return [];
2760
+ }
2761
+ return [
2762
+ this.localize(this.languageFromLengthSpec(lengthSpec), {
2763
+ zh: `第${chapterNumber}章未达到篇幅预算(${lengthSpec.hardMin}-${lengthSpec.hardMax},实际 ${finalCount})。`,
2764
+ en: `Chapter ${chapterNumber} is outside its length budget (${lengthSpec.hardMin}-${lengthSpec.hardMax}, actual ${finalCount}).`,
2765
+ }),
2766
+ ];
2767
+ }
2768
+ buildLengthTelemetry(params) {
2769
+ return {
2770
+ target: params.lengthSpec.target,
2771
+ softMin: params.lengthSpec.softMin,
2772
+ softMax: params.lengthSpec.softMax,
2773
+ hardMin: params.lengthSpec.hardMin,
2774
+ hardMax: params.lengthSpec.hardMax,
2775
+ countingMode: params.lengthSpec.countingMode,
2776
+ writerCount: params.writerCount,
2777
+ postReviseCount: params.postReviseCount,
2778
+ finalCount: params.finalCount,
2779
+ repairApplied: params.repairApplied,
2780
+ lengthWarning: params.lengthWarning,
2781
+ };
2782
+ }
2783
+ async persistAuditDriftGuidance(params) {
2784
+ const storyDir = join(params.bookDir, "story");
2785
+ const driftPath = join(storyDir, "audit_drift.md");
2786
+ const statePath = join(storyDir, "current_state.md");
2787
+ const currentState = await readFile(statePath, "utf-8").catch(() => "");
2788
+ const sanitizedState = this.stripAuditDriftCorrectionBlock(currentState).trimEnd();
2789
+ if (sanitizedState !== currentState) {
2790
+ await writeFile(statePath, sanitizedState, "utf-8");
2791
+ }
2792
+ if (params.issues.length === 0) {
2793
+ await rm(driftPath, { force: true }).catch(() => undefined);
2794
+ return;
2795
+ }
2796
+ const block = [
2797
+ this.localize(params.language, {
2798
+ zh: "# 审计纠偏",
2799
+ en: "# Audit Drift",
2800
+ }),
2801
+ "",
2802
+ this.localize(params.language, {
2803
+ zh: "## 审计纠偏(自动生成,下一章写作前参照)",
2804
+ en: "## Audit Drift Correction",
2805
+ }),
2806
+ "",
2807
+ this.localize(params.language, {
2808
+ zh: `> 第${params.chapterNumber}章审计发现以下问题,下一章写作时必须避免:`,
2809
+ en: `> Chapter ${params.chapterNumber} audit found the following issues to avoid in the next chapter:`,
2810
+ }),
2811
+ ...params.issues.map((issue) => `> - [${issue.severity}] ${issue.category}: ${issue.description}`),
2812
+ "",
2813
+ ].join("\n");
2814
+ await writeFile(driftPath, block, "utf-8");
2815
+ }
2816
+ stripAuditDriftCorrectionBlock(currentState) {
2817
+ const headers = [
2818
+ "## 审计纠偏(自动生成,下一章写作前参照)",
2819
+ "## Audit Drift Correction",
2820
+ "# 审计纠偏",
2821
+ "# Audit Drift",
2822
+ ];
2823
+ let cutIndex = -1;
2824
+ for (const header of headers) {
2825
+ const index = currentState.indexOf(header);
2826
+ if (index >= 0 && (cutIndex < 0 || index < cutIndex)) {
2827
+ cutIndex = index;
2828
+ }
2829
+ }
2830
+ if (cutIndex < 0) {
2831
+ return currentState;
2832
+ }
2833
+ return currentState.slice(0, cutIndex).trimEnd();
2834
+ }
2835
+ logLengthWarnings(lengthWarnings) {
2836
+ for (const warning of lengthWarnings) {
2837
+ this.config.logger?.warn(warning);
2838
+ }
2839
+ }
2840
+ restoreLostAuditIssues(previous, next) {
2841
+ if (next.passed || next.issues.length > 0 || previous.issues.length === 0) {
2842
+ return next;
2843
+ }
2844
+ return {
2845
+ ...next,
2846
+ issues: previous.issues,
2847
+ summary: next.summary || previous.summary,
2848
+ };
2849
+ }
2850
+ restoreActionableAuditIfLost(previous, next) {
2851
+ const auditResult = this.restoreLostAuditIssues(previous.auditResult, next.auditResult);
2852
+ if (auditResult === next.auditResult) {
2853
+ return next;
2854
+ }
2855
+ return {
2856
+ ...next,
2857
+ auditResult,
2858
+ revisionBlockingIssues: previous.revisionBlockingIssues,
2859
+ blockingCount: previous.blockingCount,
2860
+ criticalCount: previous.criticalCount,
2861
+ };
2862
+ }
2863
+ async evaluateMergedAudit(params) {
2864
+ const llmAudit = await params.auditor.auditChapter(params.bookDir, params.chapterContent, params.chapterNumber, params.book.genre, params.auditOptions);
2865
+ const aiTells = analyzeAITells(params.chapterContent, params.language);
2866
+ const sensitiveResult = analyzeSensitiveWords(params.chapterContent, undefined, params.language);
2867
+ const longSpanFatigue = await analyzeLongSpanFatigue({
2868
+ bookDir: params.bookDir,
2869
+ chapterNumber: params.chapterNumber,
2870
+ chapterContent: params.chapterContent,
2871
+ language: params.language,
2872
+ });
2873
+ const hasBlockedWords = sensitiveResult.found.some((f) => f.severity === "block");
2874
+ const issues = [
2875
+ ...llmAudit.issues,
2876
+ ...aiTells.issues,
2877
+ ...sensitiveResult.issues,
2878
+ ...longSpanFatigue.issues,
2879
+ ];
2880
+ // revisionBlockingIssues excludes long-span-fatigue issues by
2881
+ // construction (not by category name) so that an LLM-reported issue
2882
+ // sharing a category label with a long-span issue is still counted.
2883
+ const revisionBlockingIssues = [
2884
+ ...llmAudit.issues,
2885
+ ...aiTells.issues,
2886
+ ...sensitiveResult.issues,
2887
+ ];
2888
+ return {
2889
+ auditResult: {
2890
+ passed: hasBlockedWords ? false : llmAudit.passed,
2891
+ issues,
2892
+ summary: llmAudit.summary,
2893
+ tokenUsage: llmAudit.tokenUsage,
2894
+ },
2895
+ aiTellCount: aiTells.issues.length,
2896
+ blockingCount: revisionBlockingIssues.filter((issue) => issue.severity === "warning" || issue.severity === "critical").length,
2897
+ criticalCount: revisionBlockingIssues.filter((issue) => issue.severity === "critical").length,
2898
+ revisionBlockingIssues,
2899
+ };
2900
+ }
2901
+ async markBookActiveIfNeeded(bookId) {
2902
+ const book = await this.state.loadBookConfig(bookId);
2903
+ if (book.status !== "outlining")
2904
+ return;
2905
+ await this.state.saveBookConfig(bookId, {
2906
+ ...book,
2907
+ status: "active",
2908
+ updatedAt: new Date().toISOString(),
2909
+ });
2910
+ }
2911
+ async createGovernedArtifacts(book, bookDir, chapterNumber, externalContext, options) {
2912
+ const plan = await this.resolveGovernedPlan(book, bookDir, chapterNumber, externalContext, options);
2913
+ const composerCtx = this.agentCtxFor("composer", book.id);
2914
+ const composer = new ComposerAgent(composerCtx);
2915
+ const composed = await composeGovernedChapter({
2916
+ book,
2917
+ bookDir,
2918
+ chapterNumber,
2919
+ plan,
2920
+ contextBudget: contextBudgetFromClient(composerCtx.client),
2921
+ compressibleContextCompiler: (request) => composer.compileCompressibleContext(request),
2922
+ outlineSectionSelector: (request) => composer.selectOutlineSections(request),
2923
+ memorySemanticSelector: (request) => composer.selectMemoryCandidates(request),
2924
+ referenceContextProvider: (request) => selectBookReferenceContext(this.config.projectRoot, book.id, request, (selectionRequest) => composer.selectReferenceSections(selectionRequest)),
2925
+ onContextCompression: this.config.onContextCompression,
2926
+ });
2927
+ return { plan, composed };
2928
+ }
2929
+ async resolveGovernedPlan(book, bookDir, chapterNumber, externalContext, options) {
2930
+ if (options?.reuseExistingIntentWhenContextMissing &&
2931
+ (!externalContext || externalContext.trim().length === 0)) {
2932
+ const persisted = await loadPersistedPlan(bookDir, chapterNumber);
2933
+ if (persisted)
2934
+ return persisted;
2935
+ }
2936
+ const planner = new PlannerAgent(this.agentCtxFor("planner", book.id));
2937
+ const plan = await planner.planChapter({
2938
+ book,
2939
+ bookDir,
2940
+ chapterNumber,
2941
+ externalContext,
2942
+ });
2943
+ // Persist in the new memo format so subsequent compose/write phases can
2944
+ // skip the planner LLM call when no new context is supplied.
2945
+ await savePersistedPlan(bookDir, plan);
2946
+ return plan;
2947
+ }
2948
+ async emitWebhook(event, bookId, chapterNumber, data) {
2949
+ if (!this.config.notifyChannels || this.config.notifyChannels.length === 0)
2950
+ return;
2951
+ await dispatchWebhookEvent(this.config.notifyChannels, {
2952
+ event,
2953
+ bookId,
2954
+ chapterNumber,
2955
+ timestamp: new Date().toISOString(),
2956
+ data,
2957
+ });
2958
+ }
2959
+ async readChapterContent(bookDir, chapterNumber) {
2960
+ const chaptersDir = join(bookDir, "chapters");
2961
+ const files = await readdir(chaptersDir);
2962
+ const paddedNum = String(chapterNumber).padStart(4, "0");
2963
+ const chapterFile = files.find((f) => f.startsWith(paddedNum) && f.endsWith(".md"));
2964
+ if (!chapterFile) {
2965
+ throw new Error(`Chapter ${chapterNumber} file not found in ${chaptersDir}`);
2966
+ }
2967
+ const raw = await readFile(join(chaptersDir, chapterFile), "utf-8");
2968
+ // Strip the title line
2969
+ const lines = raw.split("\n");
2970
+ const contentStart = lines.findIndex((l, i) => i > 0 && l.trim().length > 0);
2971
+ return contentStart >= 0 ? lines.slice(contentStart).join("\n") : raw;
2972
+ }
2973
+ }
2974
+ //# sourceMappingURL=runner.js.map