inkos-n-core 1.2.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 (412) hide show
  1. package/dist/agents/ai-tells.d.ts +26 -0
  2. package/dist/agents/ai-tells.d.ts.map +1 -0
  3. package/dist/agents/ai-tells.js +140 -0
  4. package/dist/agents/ai-tells.js.map +1 -0
  5. package/dist/agents/architect.d.ts +34 -0
  6. package/dist/agents/architect.d.ts.map +1 -0
  7. package/dist/agents/architect.js +906 -0
  8. package/dist/agents/architect.js.map +1 -0
  9. package/dist/agents/base.d.ts +30 -0
  10. package/dist/agents/base.d.ts.map +1 -0
  11. package/dist/agents/base.js +70 -0
  12. package/dist/agents/base.js.map +1 -0
  13. package/dist/agents/chapter-analyzer.d.ts +30 -0
  14. package/dist/agents/chapter-analyzer.d.ts.map +1 -0
  15. package/dist/agents/chapter-analyzer.js +476 -0
  16. package/dist/agents/chapter-analyzer.js.map +1 -0
  17. package/dist/agents/composer.d.ts +36 -0
  18. package/dist/agents/composer.d.ts.map +1 -0
  19. package/dist/agents/composer.js +319 -0
  20. package/dist/agents/composer.js.map +1 -0
  21. package/dist/agents/consolidator.d.ts +23 -0
  22. package/dist/agents/consolidator.d.ts.map +1 -0
  23. package/dist/agents/consolidator.js +141 -0
  24. package/dist/agents/consolidator.js.map +1 -0
  25. package/dist/agents/continuity.d.ts +39 -0
  26. package/dist/agents/continuity.d.ts.map +1 -0
  27. package/dist/agents/continuity.js +612 -0
  28. package/dist/agents/continuity.js.map +1 -0
  29. package/dist/agents/detection-insights.d.ts +9 -0
  30. package/dist/agents/detection-insights.d.ts.map +1 -0
  31. package/dist/agents/detection-insights.js +54 -0
  32. package/dist/agents/detection-insights.js.map +1 -0
  33. package/dist/agents/detector.d.ts +17 -0
  34. package/dist/agents/detector.d.ts.map +1 -0
  35. package/dist/agents/detector.js +77 -0
  36. package/dist/agents/detector.js.map +1 -0
  37. package/dist/agents/en-prompt-sections.d.ts +8 -0
  38. package/dist/agents/en-prompt-sections.d.ts.map +1 -0
  39. package/dist/agents/en-prompt-sections.js +120 -0
  40. package/dist/agents/en-prompt-sections.js.map +1 -0
  41. package/dist/agents/fanfic-canon-importer.d.ts +15 -0
  42. package/dist/agents/fanfic-canon-importer.d.ts.map +1 -0
  43. package/dist/agents/fanfic-canon-importer.js +117 -0
  44. package/dist/agents/fanfic-canon-importer.js.map +1 -0
  45. package/dist/agents/fanfic-dimensions.d.ts +14 -0
  46. package/dist/agents/fanfic-dimensions.d.ts.map +1 -0
  47. package/dist/agents/fanfic-dimensions.js +63 -0
  48. package/dist/agents/fanfic-dimensions.js.map +1 -0
  49. package/dist/agents/fanfic-prompt-sections.d.ts +5 -0
  50. package/dist/agents/fanfic-prompt-sections.d.ts.map +1 -0
  51. package/dist/agents/fanfic-prompt-sections.js +85 -0
  52. package/dist/agents/fanfic-prompt-sections.js.map +1 -0
  53. package/dist/agents/foundation-reviewer.d.ts +29 -0
  54. package/dist/agents/foundation-reviewer.d.ts.map +1 -0
  55. package/dist/agents/foundation-reviewer.js +153 -0
  56. package/dist/agents/foundation-reviewer.js.map +1 -0
  57. package/dist/agents/length-normalizer.d.ts +32 -0
  58. package/dist/agents/length-normalizer.d.ts.map +1 -0
  59. package/dist/agents/length-normalizer.js +156 -0
  60. package/dist/agents/length-normalizer.js.map +1 -0
  61. package/dist/agents/observer-prompts.d.ts +10 -0
  62. package/dist/agents/observer-prompts.d.ts.map +1 -0
  63. package/dist/agents/observer-prompts.js +113 -0
  64. package/dist/agents/observer-prompts.js.map +1 -0
  65. package/dist/agents/planner.d.ts +57 -0
  66. package/dist/agents/planner.d.ts.map +1 -0
  67. package/dist/agents/planner.js +594 -0
  68. package/dist/agents/planner.js.map +1 -0
  69. package/dist/agents/post-write-validator.d.ts +34 -0
  70. package/dist/agents/post-write-validator.d.ts.map +1 -0
  71. package/dist/agents/post-write-validator.js +696 -0
  72. package/dist/agents/post-write-validator.js.map +1 -0
  73. package/dist/agents/radar-source.d.ts +38 -0
  74. package/dist/agents/radar-source.d.ts.map +1 -0
  75. package/dist/agents/radar-source.js +92 -0
  76. package/dist/agents/radar-source.js.map +1 -0
  77. package/dist/agents/radar.d.ts +24 -0
  78. package/dist/agents/radar.d.ts.map +1 -0
  79. package/dist/agents/radar.js +85 -0
  80. package/dist/agents/radar.js.map +1 -0
  81. package/dist/agents/reviser.d.ts +32 -0
  82. package/dist/agents/reviser.d.ts.map +1 -0
  83. package/dist/agents/reviser.js +282 -0
  84. package/dist/agents/reviser.js.map +1 -0
  85. package/dist/agents/rules-reader.d.ts +27 -0
  86. package/dist/agents/rules-reader.d.ts.map +1 -0
  87. package/dist/agents/rules-reader.js +99 -0
  88. package/dist/agents/rules-reader.js.map +1 -0
  89. package/dist/agents/sensitive-words.d.ts +24 -0
  90. package/dist/agents/sensitive-words.d.ts.map +1 -0
  91. package/dist/agents/sensitive-words.js +103 -0
  92. package/dist/agents/sensitive-words.js.map +1 -0
  93. package/dist/agents/settler-delta-parser.d.ts +7 -0
  94. package/dist/agents/settler-delta-parser.d.ts.map +1 -0
  95. package/dist/agents/settler-delta-parser.js +40 -0
  96. package/dist/agents/settler-delta-parser.js.map +1 -0
  97. package/dist/agents/settler-parser.d.ts +13 -0
  98. package/dist/agents/settler-parser.d.ts.map +1 -0
  99. package/dist/agents/settler-parser.js +20 -0
  100. package/dist/agents/settler-parser.js.map +1 -0
  101. package/dist/agents/settler-prompts.d.ts +22 -0
  102. package/dist/agents/settler-prompts.d.ts.map +1 -0
  103. package/dist/agents/settler-prompts.js +193 -0
  104. package/dist/agents/settler-prompts.js.map +1 -0
  105. package/dist/agents/state-validator.d.ts +26 -0
  106. package/dist/agents/state-validator.d.ts.map +1 -0
  107. package/dist/agents/state-validator.js +229 -0
  108. package/dist/agents/state-validator.js.map +1 -0
  109. package/dist/agents/style-analyzer.d.ts +11 -0
  110. package/dist/agents/style-analyzer.d.ts.map +1 -0
  111. package/dist/agents/style-analyzer.js +81 -0
  112. package/dist/agents/style-analyzer.js.map +1 -0
  113. package/dist/agents/writer-parser.d.ts +17 -0
  114. package/dist/agents/writer-parser.d.ts.map +1 -0
  115. package/dist/agents/writer-parser.js +131 -0
  116. package/dist/agents/writer-parser.js.map +1 -0
  117. package/dist/agents/writer-prompts.d.ts +11 -0
  118. package/dist/agents/writer-prompts.d.ts.map +1 -0
  119. package/dist/agents/writer-prompts.js +549 -0
  120. package/dist/agents/writer-prompts.js.map +1 -0
  121. package/dist/agents/writer.d.ts +103 -0
  122. package/dist/agents/writer.d.ts.map +1 -0
  123. package/dist/agents/writer.js +1052 -0
  124. package/dist/agents/writer.js.map +1 -0
  125. package/dist/index.d.ts +80 -0
  126. package/dist/index.d.ts.map +1 -0
  127. package/dist/index.js +84 -0
  128. package/dist/index.js.map +1 -0
  129. package/dist/interaction/edit-controller.d.ts +55 -0
  130. package/dist/interaction/edit-controller.d.ts.map +1 -0
  131. package/dist/interaction/edit-controller.js +187 -0
  132. package/dist/interaction/edit-controller.js.map +1 -0
  133. package/dist/interaction/events.d.ts +45 -0
  134. package/dist/interaction/events.d.ts.map +1 -0
  135. package/dist/interaction/events.js +32 -0
  136. package/dist/interaction/events.js.map +1 -0
  137. package/dist/interaction/intents.d.ts +96 -0
  138. package/dist/interaction/intents.d.ts.map +1 -0
  139. package/dist/interaction/intents.js +58 -0
  140. package/dist/interaction/intents.js.map +1 -0
  141. package/dist/interaction/modes.d.ts +5 -0
  142. package/dist/interaction/modes.d.ts.map +1 -0
  143. package/dist/interaction/modes.js +7 -0
  144. package/dist/interaction/modes.js.map +1 -0
  145. package/dist/interaction/nl-router.d.ts +8 -0
  146. package/dist/interaction/nl-router.d.ts.map +1 -0
  147. package/dist/interaction/nl-router.js +218 -0
  148. package/dist/interaction/nl-router.js.map +1 -0
  149. package/dist/interaction/project-control.d.ts +85 -0
  150. package/dist/interaction/project-control.d.ts.map +1 -0
  151. package/dist/interaction/project-control.js +123 -0
  152. package/dist/interaction/project-control.js.map +1 -0
  153. package/dist/interaction/project-session-store.d.ts +7 -0
  154. package/dist/interaction/project-session-store.d.ts.map +1 -0
  155. package/dist/interaction/project-session-store.js +46 -0
  156. package/dist/interaction/project-session-store.js.map +1 -0
  157. package/dist/interaction/project-tools.d.ts +20 -0
  158. package/dist/interaction/project-tools.d.ts.map +1 -0
  159. package/dist/interaction/project-tools.js +527 -0
  160. package/dist/interaction/project-tools.js.map +1 -0
  161. package/dist/interaction/request-router.d.ts +3 -0
  162. package/dist/interaction/request-router.d.ts.map +1 -0
  163. package/dist/interaction/request-router.js +5 -0
  164. package/dist/interaction/request-router.js.map +1 -0
  165. package/dist/interaction/runtime.d.ts +54 -0
  166. package/dist/interaction/runtime.d.ts.map +1 -0
  167. package/dist/interaction/runtime.js +943 -0
  168. package/dist/interaction/runtime.js.map +1 -0
  169. package/dist/interaction/session.d.ts +352 -0
  170. package/dist/interaction/session.d.ts.map +1 -0
  171. package/dist/interaction/session.js +98 -0
  172. package/dist/interaction/session.js.map +1 -0
  173. package/dist/interaction/truth-authority.d.ts +4 -0
  174. package/dist/interaction/truth-authority.d.ts.map +1 -0
  175. package/dist/interaction/truth-authority.js +37 -0
  176. package/dist/interaction/truth-authority.js.map +1 -0
  177. package/dist/llm/provider.d.ts +87 -0
  178. package/dist/llm/provider.d.ts.map +1 -0
  179. package/dist/llm/provider.js +798 -0
  180. package/dist/llm/provider.js.map +1 -0
  181. package/dist/models/book-rules.d.ts +118 -0
  182. package/dist/models/book-rules.d.ts.map +1 -0
  183. package/dist/models/book-rules.js +55 -0
  184. package/dist/models/book-rules.js.map +1 -0
  185. package/dist/models/book.d.ts +51 -0
  186. package/dist/models/book.d.ts.map +1 -0
  187. package/dist/models/book.js +27 -0
  188. package/dist/models/book.js.map +1 -0
  189. package/dist/models/chapter.d.ts +136 -0
  190. package/dist/models/chapter.d.ts.map +1 -0
  191. package/dist/models/chapter.js +38 -0
  192. package/dist/models/chapter.js.map +1 -0
  193. package/dist/models/detection.d.ts +25 -0
  194. package/dist/models/detection.d.ts.map +1 -0
  195. package/dist/models/detection.js +2 -0
  196. package/dist/models/detection.js.map +1 -0
  197. package/dist/models/genre-profile.d.ts +45 -0
  198. package/dist/models/genre-profile.d.ts.map +1 -0
  199. package/dist/models/genre-profile.js +26 -0
  200. package/dist/models/genre-profile.js.map +1 -0
  201. package/dist/models/input-governance.d.ts +516 -0
  202. package/dist/models/input-governance.d.ts.map +1 -0
  203. package/dist/models/input-governance.js +112 -0
  204. package/dist/models/input-governance.js.map +1 -0
  205. package/dist/models/length-governance.d.ts +93 -0
  206. package/dist/models/length-governance.d.ts.map +1 -0
  207. package/dist/models/length-governance.js +34 -0
  208. package/dist/models/length-governance.js.map +1 -0
  209. package/dist/models/project.d.ts +475 -0
  210. package/dist/models/project.d.ts.map +1 -0
  211. package/dist/models/project.js +100 -0
  212. package/dist/models/project.js.map +1 -0
  213. package/dist/models/runtime-state.d.ts +588 -0
  214. package/dist/models/runtime-state.d.ts.map +1 -0
  215. package/dist/models/runtime-state.js +93 -0
  216. package/dist/models/runtime-state.js.map +1 -0
  217. package/dist/models/state.d.ts +48 -0
  218. package/dist/models/state.d.ts.map +1 -0
  219. package/dist/models/state.js +6 -0
  220. package/dist/models/state.js.map +1 -0
  221. package/dist/models/style-profile.d.ts +16 -0
  222. package/dist/models/style-profile.d.ts.map +1 -0
  223. package/dist/models/style-profile.js +2 -0
  224. package/dist/models/style-profile.js.map +1 -0
  225. package/dist/notify/dispatcher.d.ts +10 -0
  226. package/dist/notify/dispatcher.d.ts.map +1 -0
  227. package/dist/notify/dispatcher.js +55 -0
  228. package/dist/notify/dispatcher.js.map +1 -0
  229. package/dist/notify/feishu.d.ts +5 -0
  230. package/dist/notify/feishu.d.ts.map +1 -0
  231. package/dist/notify/feishu.js +26 -0
  232. package/dist/notify/feishu.js.map +1 -0
  233. package/dist/notify/telegram.d.ts +6 -0
  234. package/dist/notify/telegram.d.ts.map +1 -0
  235. package/dist/notify/telegram.js +17 -0
  236. package/dist/notify/telegram.js.map +1 -0
  237. package/dist/notify/webhook.d.ts +15 -0
  238. package/dist/notify/webhook.d.ts.map +1 -0
  239. package/dist/notify/webhook.js +28 -0
  240. package/dist/notify/webhook.js.map +1 -0
  241. package/dist/notify/wechat-work.d.ts +5 -0
  242. package/dist/notify/wechat-work.d.ts.map +1 -0
  243. package/dist/notify/wechat-work.js +15 -0
  244. package/dist/notify/wechat-work.js.map +1 -0
  245. package/dist/pipeline/agent.d.ts +15 -0
  246. package/dist/pipeline/agent.d.ts.map +1 -0
  247. package/dist/pipeline/agent.js +550 -0
  248. package/dist/pipeline/agent.js.map +1 -0
  249. package/dist/pipeline/chapter-persistence.d.ts +33 -0
  250. package/dist/pipeline/chapter-persistence.d.ts.map +1 -0
  251. package/dist/pipeline/chapter-persistence.js +35 -0
  252. package/dist/pipeline/chapter-persistence.js.map +1 -0
  253. package/dist/pipeline/chapter-review-cycle.d.ts +79 -0
  254. package/dist/pipeline/chapter-review-cycle.d.ts.map +1 -0
  255. package/dist/pipeline/chapter-review-cycle.js +97 -0
  256. package/dist/pipeline/chapter-review-cycle.js.map +1 -0
  257. package/dist/pipeline/chapter-state-recovery.d.ts +59 -0
  258. package/dist/pipeline/chapter-state-recovery.d.ts.map +1 -0
  259. package/dist/pipeline/chapter-state-recovery.js +133 -0
  260. package/dist/pipeline/chapter-state-recovery.js.map +1 -0
  261. package/dist/pipeline/chapter-truth-validation.d.ts +41 -0
  262. package/dist/pipeline/chapter-truth-validation.d.ts.map +1 -0
  263. package/dist/pipeline/chapter-truth-validation.js +67 -0
  264. package/dist/pipeline/chapter-truth-validation.js.map +1 -0
  265. package/dist/pipeline/detection-runner.d.ts +31 -0
  266. package/dist/pipeline/detection-runner.d.ts.map +1 -0
  267. package/dist/pipeline/detection-runner.js +109 -0
  268. package/dist/pipeline/detection-runner.js.map +1 -0
  269. package/dist/pipeline/persisted-governed-plan.d.ts +4 -0
  270. package/dist/pipeline/persisted-governed-plan.d.ts.map +1 -0
  271. package/dist/pipeline/persisted-governed-plan.js +85 -0
  272. package/dist/pipeline/persisted-governed-plan.js.map +1 -0
  273. package/dist/pipeline/runner.d.ts +212 -0
  274. package/dist/pipeline/runner.d.ts.map +1 -0
  275. package/dist/pipeline/runner.js +2265 -0
  276. package/dist/pipeline/runner.js.map +1 -0
  277. package/dist/pipeline/scheduler.d.ts +58 -0
  278. package/dist/pipeline/scheduler.d.ts.map +1 -0
  279. package/dist/pipeline/scheduler.js +322 -0
  280. package/dist/pipeline/scheduler.js.map +1 -0
  281. package/dist/state/manager.d.ts +48 -0
  282. package/dist/state/manager.d.ts.map +1 -0
  283. package/dist/state/manager.js +435 -0
  284. package/dist/state/manager.js.map +1 -0
  285. package/dist/state/memory-db.d.ts +77 -0
  286. package/dist/state/memory-db.d.ts.map +1 -0
  287. package/dist/state/memory-db.js +249 -0
  288. package/dist/state/memory-db.js.map +1 -0
  289. package/dist/state/runtime-state-store.d.ts +25 -0
  290. package/dist/state/runtime-state-store.d.ts.map +1 -0
  291. package/dist/state/runtime-state-store.js +108 -0
  292. package/dist/state/runtime-state-store.js.map +1 -0
  293. package/dist/state/state-bootstrap.d.ts +21 -0
  294. package/dist/state/state-bootstrap.d.ts.map +1 -0
  295. package/dist/state/state-bootstrap.js +434 -0
  296. package/dist/state/state-bootstrap.js.map +1 -0
  297. package/dist/state/state-projections.d.ts +5 -0
  298. package/dist/state/state-projections.d.ts.map +1 -0
  299. package/dist/state/state-projections.js +166 -0
  300. package/dist/state/state-projections.js.map +1 -0
  301. package/dist/state/state-reducer.d.ts +13 -0
  302. package/dist/state/state-reducer.d.ts.map +1 -0
  303. package/dist/state/state-reducer.js +194 -0
  304. package/dist/state/state-reducer.js.map +1 -0
  305. package/dist/state/state-validator.d.ts +12 -0
  306. package/dist/state/state-validator.d.ts.map +1 -0
  307. package/dist/state/state-validator.js +67 -0
  308. package/dist/state/state-validator.js.map +1 -0
  309. package/dist/utils/analytics.d.ts +39 -0
  310. package/dist/utils/analytics.d.ts.map +1 -0
  311. package/dist/utils/analytics.js +50 -0
  312. package/dist/utils/analytics.js.map +1 -0
  313. package/dist/utils/cadence-policy.d.ts +36 -0
  314. package/dist/utils/cadence-policy.d.ts.map +1 -0
  315. package/dist/utils/cadence-policy.js +38 -0
  316. package/dist/utils/cadence-policy.js.map +1 -0
  317. package/dist/utils/chapter-cadence.d.ts +34 -0
  318. package/dist/utils/chapter-cadence.d.ts.map +1 -0
  319. package/dist/utils/chapter-cadence.js +142 -0
  320. package/dist/utils/chapter-cadence.js.map +1 -0
  321. package/dist/utils/chapter-splitter.d.ts +18 -0
  322. package/dist/utils/chapter-splitter.d.ts.map +1 -0
  323. package/dist/utils/chapter-splitter.js +60 -0
  324. package/dist/utils/chapter-splitter.js.map +1 -0
  325. package/dist/utils/config-loader.d.ts +15 -0
  326. package/dist/utils/config-loader.d.ts.map +1 -0
  327. package/dist/utils/config-loader.js +136 -0
  328. package/dist/utils/config-loader.js.map +1 -0
  329. package/dist/utils/context-filter.d.ts +20 -0
  330. package/dist/utils/context-filter.d.ts.map +1 -0
  331. package/dist/utils/context-filter.js +134 -0
  332. package/dist/utils/context-filter.js.map +1 -0
  333. package/dist/utils/governed-context.d.ts +11 -0
  334. package/dist/utils/governed-context.d.ts.map +1 -0
  335. package/dist/utils/governed-context.js +42 -0
  336. package/dist/utils/governed-context.js.map +1 -0
  337. package/dist/utils/governed-working-set.d.ts +18 -0
  338. package/dist/utils/governed-working-set.d.ts.map +1 -0
  339. package/dist/utils/governed-working-set.js +296 -0
  340. package/dist/utils/governed-working-set.js.map +1 -0
  341. package/dist/utils/hook-agenda.d.ts +21 -0
  342. package/dist/utils/hook-agenda.d.ts.map +1 -0
  343. package/dist/utils/hook-agenda.js +95 -0
  344. package/dist/utils/hook-agenda.js.map +1 -0
  345. package/dist/utils/hook-arbiter.d.ts +15 -0
  346. package/dist/utils/hook-arbiter.d.ts.map +1 -0
  347. package/dist/utils/hook-arbiter.js +268 -0
  348. package/dist/utils/hook-arbiter.js.map +1 -0
  349. package/dist/utils/hook-governance.d.ts +28 -0
  350. package/dist/utils/hook-governance.d.ts.map +1 -0
  351. package/dist/utils/hook-governance.js +144 -0
  352. package/dist/utils/hook-governance.js.map +1 -0
  353. package/dist/utils/hook-health.d.ts +15 -0
  354. package/dist/utils/hook-health.d.ts.map +1 -0
  355. package/dist/utils/hook-health.js +128 -0
  356. package/dist/utils/hook-health.js.map +1 -0
  357. package/dist/utils/hook-lifecycle.d.ts +34 -0
  358. package/dist/utils/hook-lifecycle.d.ts.map +1 -0
  359. package/dist/utils/hook-lifecycle.js +125 -0
  360. package/dist/utils/hook-lifecycle.js.map +1 -0
  361. package/dist/utils/hook-policy.d.ts +74 -0
  362. package/dist/utils/hook-policy.d.ts.map +1 -0
  363. package/dist/utils/hook-policy.js +126 -0
  364. package/dist/utils/hook-policy.js.map +1 -0
  365. package/dist/utils/length-metrics.d.ts +10 -0
  366. package/dist/utils/length-metrics.d.ts.map +1 -0
  367. package/dist/utils/length-metrics.js +85 -0
  368. package/dist/utils/length-metrics.js.map +1 -0
  369. package/dist/utils/logger.d.ts +31 -0
  370. package/dist/utils/logger.d.ts.map +1 -0
  371. package/dist/utils/logger.js +79 -0
  372. package/dist/utils/logger.js.map +1 -0
  373. package/dist/utils/long-span-fatigue.d.ts +28 -0
  374. package/dist/utils/long-span-fatigue.d.ts.map +1 -0
  375. package/dist/utils/long-span-fatigue.js +406 -0
  376. package/dist/utils/long-span-fatigue.js.map +1 -0
  377. package/dist/utils/memory-retrieval.d.ts +25 -0
  378. package/dist/utils/memory-retrieval.d.ts.map +1 -0
  379. package/dist/utils/memory-retrieval.js +319 -0
  380. package/dist/utils/memory-retrieval.js.map +1 -0
  381. package/dist/utils/pov-filter.d.ts +30 -0
  382. package/dist/utils/pov-filter.d.ts.map +1 -0
  383. package/dist/utils/pov-filter.js +129 -0
  384. package/dist/utils/pov-filter.js.map +1 -0
  385. package/dist/utils/spot-fix-patches.d.ts +14 -0
  386. package/dist/utils/spot-fix-patches.d.ts.map +1 -0
  387. package/dist/utils/spot-fix-patches.js +75 -0
  388. package/dist/utils/spot-fix-patches.js.map +1 -0
  389. package/dist/utils/story-markdown.d.ts +13 -0
  390. package/dist/utils/story-markdown.d.ts.map +1 -0
  391. package/dist/utils/story-markdown.js +218 -0
  392. package/dist/utils/story-markdown.js.map +1 -0
  393. package/dist/utils/web-search.d.ts +23 -0
  394. package/dist/utils/web-search.d.ts.map +1 -0
  395. package/dist/utils/web-search.js +68 -0
  396. package/dist/utils/web-search.js.map +1 -0
  397. package/genres/cozy.md +43 -0
  398. package/genres/cultivation.md +42 -0
  399. package/genres/dungeon-core.md +40 -0
  400. package/genres/horror.md +51 -0
  401. package/genres/isekai.md +43 -0
  402. package/genres/litrpg.md +43 -0
  403. package/genres/other.md +24 -0
  404. package/genres/progression.md +41 -0
  405. package/genres/romantasy.md +45 -0
  406. package/genres/sci-fi.md +42 -0
  407. package/genres/system-apocalypse.md +40 -0
  408. package/genres/tower-climber.md +41 -0
  409. package/genres/urban.md +53 -0
  410. package/genres/xianxia.md +46 -0
  411. package/genres/xuanhuan.md +64 -0
  412. package/package.json +58 -0
@@ -0,0 +1,1052 @@
1
+ import { BaseAgent } from "./base.js";
2
+ import { buildWriterSystemPrompt } from "./writer-prompts.js";
3
+ import { buildSettlerSystemPrompt, buildSettlerUserPrompt } from "./settler-prompts.js";
4
+ import { buildObserverSystemPrompt, buildObserverUserPrompt } from "./observer-prompts.js";
5
+ import { parseSettlerDeltaOutput } from "./settler-delta-parser.js";
6
+ import { parseSettlementOutput } from "./settler-parser.js";
7
+ import { readGenreProfile, readBookRules } from "./rules-reader.js";
8
+ import { detectCrossChapterRepetition, detectParagraphLengthDrift, validatePostWrite, } from "./post-write-validator.js";
9
+ import { analyzeAITells } from "./ai-tells.js";
10
+ import { buildLengthSpec, countChapterLength } from "../utils/length-metrics.js";
11
+ import { filterHooks, filterSummaries, filterSubplots, filterEmotionalArcs, filterCharacterMatrix } from "../utils/context-filter.js";
12
+ import { buildGovernedMemoryEvidenceBlocks } from "../utils/governed-context.js";
13
+ import { buildGovernedCharacterMatrixWorkingSet, buildGovernedHookWorkingSet, mergeCharacterMatrixMarkdown, mergeTableMarkdownByKey, } from "../utils/governed-working-set.js";
14
+ import { extractPOVFromOutline, filterMatrixByPOV, filterHooksByPOV } from "../utils/pov-filter.js";
15
+ import { parseCreativeOutput } from "./writer-parser.js";
16
+ import { buildRuntimeStateArtifacts, saveRuntimeStateSnapshot } from "../state/runtime-state-store.js";
17
+ import { parsePendingHooksMarkdown } from "../utils/memory-retrieval.js";
18
+ import { analyzeHookHealth } from "../utils/hook-health.js";
19
+ import { buildEnglishVarianceBrief } from "../utils/long-span-fatigue.js";
20
+ import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
21
+ import { join } from "node:path";
22
+ export class WriterAgent extends BaseAgent {
23
+ get name() {
24
+ return "writer";
25
+ }
26
+ localize(language, messages) {
27
+ return language === "en" ? messages.en : messages.zh;
28
+ }
29
+ logInfo(language, messages) {
30
+ this.ctx.logger?.info(this.localize(language, messages));
31
+ }
32
+ logWarn(language, messages) {
33
+ this.ctx.logger?.warn(this.localize(language, messages));
34
+ }
35
+ async writeChapter(input) {
36
+ const { book, bookDir, chapterNumber } = input;
37
+ const [storyBible, volumeOutline, styleGuide, currentState, ledger, hooks, chapterSummaries, subplotBoard, emotionalArcs, characterMatrix, styleProfileRaw, parentCanon, fanficCanonRaw,] = await Promise.all([
38
+ this.readFileOrDefault(join(bookDir, "story/story_bible.md")),
39
+ this.readFileOrDefault(join(bookDir, "story/volume_outline.md")),
40
+ this.readFileOrDefault(join(bookDir, "story/style_guide.md")),
41
+ this.readFileOrDefault(join(bookDir, "story/current_state.md")),
42
+ this.readFileOrDefault(join(bookDir, "story/particle_ledger.md")),
43
+ this.readFileOrDefault(join(bookDir, "story/pending_hooks.md")),
44
+ this.readFileOrDefault(join(bookDir, "story/chapter_summaries.md")),
45
+ this.readFileOrDefault(join(bookDir, "story/subplot_board.md")),
46
+ this.readFileOrDefault(join(bookDir, "story/emotional_arcs.md")),
47
+ this.readFileOrDefault(join(bookDir, "story/character_matrix.md")),
48
+ this.readFileOrDefault(join(bookDir, "story/style_profile.json")),
49
+ this.readFileOrDefault(join(bookDir, "story/parent_canon.md")),
50
+ this.readFileOrDefault(join(bookDir, "story/fanfic_canon.md")),
51
+ ]);
52
+ const recentChapters = await this.loadRecentChapters(bookDir, chapterNumber);
53
+ // Load more chapters for dialogue fingerprint extraction (voice consistency over longer span)
54
+ const fingerprintChapters = await this.loadRecentChapters(bookDir, chapterNumber, 5);
55
+ // Load genre profile + book rules
56
+ const { profile: genreProfile, body: genreBody } = await readGenreProfile(this.ctx.projectRoot, book.genre);
57
+ const parsedBookRules = await readBookRules(bookDir);
58
+ const bookRules = parsedBookRules?.rules ?? null;
59
+ const bookRulesBody = parsedBookRules?.body ?? "";
60
+ const styleFingerprint = this.buildStyleFingerprint(styleProfileRaw);
61
+ const dialogueFingerprints = this.extractDialogueFingerprints(fingerprintChapters, storyBible);
62
+ const relevantSummaries = this.findRelevantSummaries(chapterSummaries, volumeOutline, chapterNumber);
63
+ const hasParentCanon = parentCanon !== "(文件尚未创建)";
64
+ const hasFanficCanon = fanficCanonRaw !== "(文件尚未创建)";
65
+ const resolvedLanguage = book.language ?? genreProfile.language;
66
+ const targetWords = input.lengthSpec?.target ?? input.wordCountOverride ?? book.chapterWordCount;
67
+ const resolvedLengthSpec = input.lengthSpec ?? buildLengthSpec(targetWords, resolvedLanguage);
68
+ const governedMemoryBlocks = input.contextPackage
69
+ ? buildGovernedMemoryEvidenceBlocks(input.contextPackage, resolvedLanguage)
70
+ : undefined;
71
+ const englishVarianceBrief = resolvedLanguage === "en"
72
+ ? await buildEnglishVarianceBrief({
73
+ bookDir,
74
+ chapterNumber,
75
+ })
76
+ : null;
77
+ // Build fanfic context if fanfic_canon.md exists
78
+ const fanficContext = hasFanficCanon && bookRules?.fanficMode
79
+ ? {
80
+ fanficCanon: fanficCanonRaw,
81
+ fanficMode: bookRules.fanficMode,
82
+ allowedDeviations: bookRules.allowedDeviations ?? [],
83
+ }
84
+ : undefined;
85
+ // ── Phase 1: Creative writing (temperature 0.7) ──
86
+ const creativeSystemPrompt = buildWriterSystemPrompt(book, genreProfile, bookRules, bookRulesBody, genreBody, styleGuide, styleFingerprint, chapterNumber, "creative", fanficContext, resolvedLanguage, input.chapterIntent ? "governed" : "legacy", resolvedLengthSpec);
87
+ const creativeUserPrompt = input.chapterIntent && input.contextPackage && input.ruleStack
88
+ ? this.buildGovernedUserPrompt({
89
+ chapterNumber,
90
+ chapterIntent: input.chapterIntent,
91
+ contextPackage: input.contextPackage,
92
+ ruleStack: input.ruleStack,
93
+ trace: input.trace,
94
+ lengthSpec: resolvedLengthSpec,
95
+ language: book.language ?? genreProfile.language,
96
+ varianceBrief: englishVarianceBrief?.text,
97
+ selectedEvidenceBlock: this.joinGovernedEvidenceBlocks(governedMemoryBlocks),
98
+ })
99
+ : (() => {
100
+ // Smart context filtering: inject only relevant parts of truth files
101
+ const filteredHooks = filterHooks(hooks);
102
+ const filteredSummaries = filterSummaries(chapterSummaries, chapterNumber);
103
+ const filteredSubplots = filterSubplots(subplotBoard);
104
+ const filteredArcs = filterEmotionalArcs(emotionalArcs, chapterNumber);
105
+ const filteredMatrix = filterCharacterMatrix(characterMatrix, volumeOutline, bookRules?.protagonist?.name);
106
+ // POV-aware filtering: limit context to what the POV character knows
107
+ const povCharacter = extractPOVFromOutline(volumeOutline, chapterNumber);
108
+ const povFilteredMatrix = povCharacter
109
+ ? filterMatrixByPOV(filteredMatrix, povCharacter)
110
+ : filteredMatrix;
111
+ const povFilteredHooks = povCharacter
112
+ ? filterHooksByPOV(filteredHooks, povCharacter, chapterSummaries)
113
+ : filteredHooks;
114
+ return this.buildUserPrompt({
115
+ chapterNumber,
116
+ storyBible,
117
+ volumeOutline,
118
+ currentState,
119
+ ledger: genreProfile.numericalSystem ? ledger : "",
120
+ hooks: povFilteredHooks,
121
+ recentChapters,
122
+ lengthSpec: resolvedLengthSpec,
123
+ externalContext: input.externalContext,
124
+ chapterSummaries: filteredSummaries,
125
+ subplotBoard: filteredSubplots,
126
+ emotionalArcs: filteredArcs,
127
+ characterMatrix: povFilteredMatrix,
128
+ dialogueFingerprints,
129
+ relevantSummaries,
130
+ parentCanon: hasParentCanon ? parentCanon : undefined,
131
+ language: book.language ?? genreProfile.language,
132
+ });
133
+ })();
134
+ const creativeTemperature = input.temperatureOverride ?? 0.7;
135
+ this.logInfo(resolvedLanguage, {
136
+ zh: `阶段 1:创作正文(第${chapterNumber}章)`,
137
+ en: `Phase 1: creative writing for chapter ${chapterNumber}`,
138
+ });
139
+ // Scale maxTokens to chapter word count (Chinese ≈ 1.5 tokens/char)
140
+ const creativeMaxTokens = Math.max(8192, Math.ceil(targetWords * 2));
141
+ const creativeResponse = await this.chat([
142
+ { role: "system", content: creativeSystemPrompt },
143
+ { role: "user", content: creativeUserPrompt },
144
+ ], { maxTokens: creativeMaxTokens, temperature: creativeTemperature });
145
+ const creativeUsage = creativeResponse.usage;
146
+ const creative = parseCreativeOutput(chapterNumber, creativeResponse.content, resolvedLengthSpec.countingMode);
147
+ // ── Phase 2: State settlement (temperature 0.3) ──
148
+ this.logInfo(resolvedLanguage, {
149
+ zh: `阶段 2:状态结算(第${chapterNumber}章,${creative.wordCount}字)`,
150
+ en: `Phase 2: state settlement for chapter ${chapterNumber} (${creative.wordCount} words)`,
151
+ });
152
+ const isGovernedSettlement = Boolean(input.chapterIntent && input.contextPackage && input.ruleStack);
153
+ const filteredHooksForSettlement = isGovernedSettlement && input.contextPackage
154
+ ? buildGovernedHookWorkingSet({
155
+ hooksMarkdown: hooks,
156
+ contextPackage: input.contextPackage,
157
+ chapterIntent: input.chapterIntent,
158
+ chapterNumber,
159
+ language: resolvedLanguage,
160
+ })
161
+ : hooks;
162
+ const filteredSubplotsForSettlement = isGovernedSettlement
163
+ ? filterSubplots(subplotBoard)
164
+ : subplotBoard;
165
+ const filteredArcsForSettlement = isGovernedSettlement
166
+ ? filterEmotionalArcs(emotionalArcs, chapterNumber)
167
+ : emotionalArcs;
168
+ const filteredMatrixForSettlement = isGovernedSettlement
169
+ ? buildGovernedCharacterMatrixWorkingSet({
170
+ matrixMarkdown: characterMatrix,
171
+ chapterIntent: input.chapterIntent ?? volumeOutline,
172
+ contextPackage: input.contextPackage,
173
+ protagonistName: bookRules?.protagonist?.name,
174
+ })
175
+ : characterMatrix;
176
+ const settleResult = await this.settle({
177
+ book,
178
+ genreProfile,
179
+ bookRules,
180
+ chapterNumber,
181
+ title: creative.title,
182
+ content: creative.content,
183
+ currentState,
184
+ ledger: genreProfile.numericalSystem ? ledger : "",
185
+ hooks: filteredHooksForSettlement,
186
+ chapterSummaries: input.contextPackage ? filterSummaries(chapterSummaries, chapterNumber) : chapterSummaries,
187
+ subplotBoard: filteredSubplotsForSettlement,
188
+ emotionalArcs: filteredArcsForSettlement,
189
+ characterMatrix: filteredMatrixForSettlement,
190
+ volumeOutline,
191
+ selectedEvidenceBlock: governedMemoryBlocks
192
+ ? this.joinGovernedEvidenceBlocks(governedMemoryBlocks)
193
+ : undefined,
194
+ chapterIntent: input.chapterIntent,
195
+ contextPackage: input.contextPackage,
196
+ ruleStack: input.ruleStack,
197
+ validationFeedback: undefined,
198
+ originalHooks: hooks,
199
+ originalSubplots: subplotBoard,
200
+ originalEmotionalArcs: emotionalArcs,
201
+ originalCharacterMatrix: characterMatrix,
202
+ });
203
+ const settlement = settleResult.settlement;
204
+ const settleUsage = settleResult.usage;
205
+ const runtimeStateArtifacts = await this.buildRuntimeStateArtifactsIfPresent(bookDir, settlement.runtimeStateDelta, resolvedLanguage, chapterNumber);
206
+ const resolvedRuntimeStateDelta = runtimeStateArtifacts?.resolvedDelta ?? settlement.runtimeStateDelta;
207
+ const priorHookIds = new Set(parsePendingHooksMarkdown(hooks).map((hook) => hook.hookId));
208
+ const hookHealthIssues = resolvedRuntimeStateDelta
209
+ && (runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot)
210
+ ? analyzeHookHealth({
211
+ language: resolvedLanguage,
212
+ chapterNumber,
213
+ targetChapters: book.targetChapters,
214
+ hooks: (runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot).hooks.hooks,
215
+ delta: resolvedRuntimeStateDelta,
216
+ existingHookIds: [...priorHookIds],
217
+ })
218
+ : [];
219
+ // ── Post-write validation (regex + rule-based, zero LLM cost) ──
220
+ const ruleViolations = [
221
+ ...validatePostWrite(creative.content, genreProfile, bookRules, resolvedLanguage),
222
+ ...detectCrossChapterRepetition(creative.content, fingerprintChapters, resolvedLanguage),
223
+ ...detectParagraphLengthDrift(creative.content, fingerprintChapters, resolvedLanguage),
224
+ ];
225
+ const aiTellIssues = analyzeAITells(creative.content, resolvedLanguage).issues;
226
+ const postWriteErrors = ruleViolations.filter(v => v.severity === "error");
227
+ const postWriteWarnings = ruleViolations.filter(v => v.severity === "warning");
228
+ if (ruleViolations.length > 0) {
229
+ this.logWarn(resolvedLanguage, {
230
+ zh: `后写校验:第${chapterNumber}章 ${postWriteErrors.length} 个错误,${postWriteWarnings.length} 个警告`,
231
+ en: `Post-write: ${postWriteErrors.length} errors, ${postWriteWarnings.length} warnings in chapter ${chapterNumber}`,
232
+ });
233
+ for (const v of ruleViolations) {
234
+ this.ctx.logger?.warn(`[${v.severity}] ${v.rule}: ${v.description}`);
235
+ }
236
+ }
237
+ if (aiTellIssues.length > 0) {
238
+ this.logWarn(resolvedLanguage, {
239
+ zh: `AI 味检查:第${chapterNumber}章发现 ${aiTellIssues.length} 个问题`,
240
+ en: `AI-tell check: ${aiTellIssues.length} issues in chapter ${chapterNumber}`,
241
+ });
242
+ for (const issue of aiTellIssues) {
243
+ this.ctx.logger?.warn(`[${issue.severity}] ${issue.category}: ${issue.description}`);
244
+ }
245
+ }
246
+ if (hookHealthIssues.length > 0) {
247
+ this.logWarn(resolvedLanguage, {
248
+ zh: `伏笔健康:第${chapterNumber}章发现 ${hookHealthIssues.length} 条警告`,
249
+ en: `Hook health: ${hookHealthIssues.length} warning(s) in chapter ${chapterNumber}`,
250
+ });
251
+ for (const issue of hookHealthIssues) {
252
+ this.ctx.logger?.warn(`[${issue.severity}] ${issue.category}: ${issue.description}`);
253
+ }
254
+ }
255
+ // ── Merge into WriteChapterOutput ──
256
+ const tokenUsage = {
257
+ promptTokens: creativeUsage.promptTokens + settleUsage.promptTokens,
258
+ completionTokens: creativeUsage.completionTokens + settleUsage.completionTokens,
259
+ totalTokens: creativeUsage.totalTokens + settleUsage.totalTokens,
260
+ };
261
+ return {
262
+ chapterNumber,
263
+ title: creative.title,
264
+ content: creative.content,
265
+ wordCount: creative.wordCount,
266
+ preWriteCheck: creative.preWriteCheck,
267
+ postSettlement: settlement.postSettlement,
268
+ runtimeStateDelta: resolvedRuntimeStateDelta,
269
+ runtimeStateSnapshot: runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot,
270
+ updatedState: runtimeStateArtifacts?.currentStateMarkdown ?? settlement.updatedState,
271
+ updatedLedger: settlement.updatedLedger,
272
+ updatedHooks: runtimeStateArtifacts?.hooksMarkdown ?? settlement.updatedHooks,
273
+ chapterSummary: resolvedRuntimeStateDelta
274
+ ? this.renderDeltaSummaryRow(resolvedRuntimeStateDelta)
275
+ : settlement.chapterSummary,
276
+ updatedChapterSummaries: runtimeStateArtifacts?.chapterSummariesMarkdown,
277
+ updatedSubplots: settlement.updatedSubplots,
278
+ updatedEmotionalArcs: settlement.updatedEmotionalArcs,
279
+ updatedCharacterMatrix: settlement.updatedCharacterMatrix,
280
+ postWriteErrors,
281
+ postWriteWarnings,
282
+ hookHealthIssues,
283
+ tokenUsage,
284
+ };
285
+ }
286
+ async settleChapterState(input) {
287
+ const [currentState, ledger, hooks, chapterSummaries, subplotBoard, emotionalArcs, characterMatrix, volumeOutline,] = await Promise.all([
288
+ this.readFileOrDefault(join(input.bookDir, "story/current_state.md")),
289
+ this.readFileOrDefault(join(input.bookDir, "story/particle_ledger.md")),
290
+ this.readFileOrDefault(join(input.bookDir, "story/pending_hooks.md")),
291
+ this.readFileOrDefault(join(input.bookDir, "story/chapter_summaries.md")),
292
+ this.readFileOrDefault(join(input.bookDir, "story/subplot_board.md")),
293
+ this.readFileOrDefault(join(input.bookDir, "story/emotional_arcs.md")),
294
+ this.readFileOrDefault(join(input.bookDir, "story/character_matrix.md")),
295
+ this.readFileOrDefault(join(input.bookDir, "story/volume_outline.md")),
296
+ ]);
297
+ const { profile: genreProfile } = await readGenreProfile(this.ctx.projectRoot, input.book.genre);
298
+ const parsedBookRules = await readBookRules(input.bookDir);
299
+ const bookRules = parsedBookRules?.rules ?? null;
300
+ const resolvedLanguage = input.book.language ?? genreProfile.language;
301
+ const governedMemoryBlocks = input.contextPackage
302
+ ? buildGovernedMemoryEvidenceBlocks(input.contextPackage, resolvedLanguage)
303
+ : undefined;
304
+ const settleResult = await this.settle({
305
+ book: input.book,
306
+ genreProfile,
307
+ bookRules,
308
+ chapterNumber: input.chapterNumber,
309
+ title: input.title,
310
+ content: input.content,
311
+ currentState,
312
+ ledger: genreProfile.numericalSystem ? ledger : "",
313
+ hooks,
314
+ chapterSummaries,
315
+ subplotBoard,
316
+ emotionalArcs,
317
+ characterMatrix,
318
+ volumeOutline,
319
+ selectedEvidenceBlock: governedMemoryBlocks
320
+ ? this.joinGovernedEvidenceBlocks(governedMemoryBlocks)
321
+ : undefined,
322
+ chapterIntent: input.chapterIntent,
323
+ contextPackage: input.contextPackage,
324
+ ruleStack: input.ruleStack,
325
+ validationFeedback: input.validationFeedback,
326
+ originalHooks: hooks,
327
+ originalSubplots: subplotBoard,
328
+ originalEmotionalArcs: emotionalArcs,
329
+ originalCharacterMatrix: characterMatrix,
330
+ });
331
+ const settlement = settleResult.settlement;
332
+ const runtimeStateArtifacts = await this.buildRuntimeStateArtifactsIfPresent(input.bookDir, settlement.runtimeStateDelta, resolvedLanguage, input.chapterNumber, input.allowReapply);
333
+ return {
334
+ chapterNumber: input.chapterNumber,
335
+ title: input.title,
336
+ content: input.content,
337
+ wordCount: countChapterLength(input.content, resolvedLanguage === "en" ? "en_words" : "zh_chars"),
338
+ preWriteCheck: "",
339
+ postSettlement: settlement.postSettlement,
340
+ runtimeStateDelta: runtimeStateArtifacts?.resolvedDelta ?? settlement.runtimeStateDelta,
341
+ runtimeStateSnapshot: runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot,
342
+ updatedState: runtimeStateArtifacts?.currentStateMarkdown ?? settlement.updatedState,
343
+ updatedLedger: settlement.updatedLedger,
344
+ updatedHooks: runtimeStateArtifacts?.hooksMarkdown ?? settlement.updatedHooks,
345
+ chapterSummary: settlement.runtimeStateDelta
346
+ ? this.renderDeltaSummaryRow(settlement.runtimeStateDelta)
347
+ : settlement.chapterSummary,
348
+ updatedChapterSummaries: runtimeStateArtifacts?.chapterSummariesMarkdown,
349
+ updatedSubplots: settlement.updatedSubplots,
350
+ updatedEmotionalArcs: settlement.updatedEmotionalArcs,
351
+ updatedCharacterMatrix: settlement.updatedCharacterMatrix,
352
+ postWriteErrors: [],
353
+ postWriteWarnings: [],
354
+ tokenUsage: settleResult.usage,
355
+ };
356
+ }
357
+ async settle(params) {
358
+ // Phase 2a: Observer — extract all facts from the chapter
359
+ const resolvedLang = params.book.language ?? params.genreProfile.language;
360
+ const observerSystem = buildObserverSystemPrompt(params.book, params.genreProfile, resolvedLang);
361
+ const observerUser = buildObserverUserPrompt(params.chapterNumber, params.title, params.content, resolvedLang);
362
+ this.logInfo(resolvedLang, {
363
+ zh: `阶段 2a:提取第${params.chapterNumber}章事实`,
364
+ en: `Phase 2a: observing facts for chapter ${params.chapterNumber}`,
365
+ });
366
+ const observerResponse = await this.chat([
367
+ { role: "system", content: observerSystem },
368
+ { role: "user", content: observerUser },
369
+ ], { maxTokens: 4096, temperature: 0.5 });
370
+ const observations = observerResponse.content;
371
+ // Phase 2b: Reflector — merge observations into truth files
372
+ this.logInfo(resolvedLang, {
373
+ zh: "阶段 2b:把观察结果回写到真相文件",
374
+ en: "Phase 2b: reflecting observations into truth files",
375
+ });
376
+ const settlerSystem = buildSettlerSystemPrompt(params.book, params.genreProfile, params.bookRules, resolvedLang);
377
+ const governedControlBlock = params.chapterIntent && params.contextPackage && params.ruleStack
378
+ ? this.buildSettlerGovernedControlBlock(params.chapterIntent, params.contextPackage, params.ruleStack, resolvedLang)
379
+ : undefined;
380
+ const settlerUser = buildSettlerUserPrompt({
381
+ chapterNumber: params.chapterNumber,
382
+ title: params.title,
383
+ content: params.content,
384
+ currentState: params.currentState,
385
+ ledger: params.ledger,
386
+ hooks: params.hooks,
387
+ chapterSummaries: params.chapterSummaries,
388
+ subplotBoard: params.subplotBoard,
389
+ emotionalArcs: params.emotionalArcs,
390
+ characterMatrix: params.characterMatrix,
391
+ volumeOutline: params.volumeOutline,
392
+ observations,
393
+ selectedEvidenceBlock: params.selectedEvidenceBlock,
394
+ governedControlBlock,
395
+ validationFeedback: params.validationFeedback,
396
+ });
397
+ // Settler outputs all truth files — scale with content size
398
+ const settlerMaxTokens = Math.max(8192, Math.ceil(params.content.length * 0.8));
399
+ const response = await this.chat([
400
+ { role: "system", content: settlerSystem },
401
+ { role: "user", content: settlerUser },
402
+ ], { maxTokens: settlerMaxTokens, temperature: 0.3 });
403
+ let mergedSettlement;
404
+ try {
405
+ const deltaOutput = parseSettlerDeltaOutput(response.content);
406
+ mergedSettlement = {
407
+ postSettlement: deltaOutput.postSettlement,
408
+ runtimeStateDelta: deltaOutput.runtimeStateDelta,
409
+ updatedState: "",
410
+ updatedLedger: "",
411
+ updatedHooks: "",
412
+ chapterSummary: "",
413
+ updatedSubplots: "",
414
+ updatedEmotionalArcs: "",
415
+ updatedCharacterMatrix: "",
416
+ };
417
+ }
418
+ catch {
419
+ const settlement = parseSettlementOutput(response.content, params.genreProfile);
420
+ mergedSettlement = governedControlBlock
421
+ ? {
422
+ ...settlement,
423
+ updatedHooks: mergeTableMarkdownByKey(params.originalHooks, settlement.updatedHooks, [0]),
424
+ updatedSubplots: settlement.updatedSubplots
425
+ ? mergeTableMarkdownByKey(params.originalSubplots, settlement.updatedSubplots, [0])
426
+ : settlement.updatedSubplots,
427
+ updatedEmotionalArcs: settlement.updatedEmotionalArcs
428
+ ? mergeTableMarkdownByKey(params.originalEmotionalArcs, settlement.updatedEmotionalArcs, [0, 1])
429
+ : settlement.updatedEmotionalArcs,
430
+ updatedCharacterMatrix: settlement.updatedCharacterMatrix
431
+ ? mergeCharacterMatrixMarkdown(params.originalCharacterMatrix, settlement.updatedCharacterMatrix)
432
+ : settlement.updatedCharacterMatrix,
433
+ }
434
+ : settlement;
435
+ }
436
+ return {
437
+ settlement: mergedSettlement,
438
+ usage: response.usage,
439
+ };
440
+ }
441
+ async saveChapter(bookDir, output, numericalSystem = true, language = "zh") {
442
+ const chaptersDir = join(bookDir, "chapters");
443
+ const storyDir = join(bookDir, "story");
444
+ await mkdir(chaptersDir, { recursive: true });
445
+ const paddedNum = String(output.chapterNumber).padStart(4, "0");
446
+ const filename = `${paddedNum}_${this.sanitizeFilename(output.title)}.md`;
447
+ const heading = language === "en"
448
+ ? `# Chapter ${output.chapterNumber}: ${output.title}`
449
+ : `# 第${output.chapterNumber}章 ${output.title}`;
450
+ const chapterContent = [
451
+ heading,
452
+ "",
453
+ output.content,
454
+ ].join("\n");
455
+ const runtimeStateArtifacts = await this.resolveRuntimeStateArtifactsForOutput(bookDir, output, language);
456
+ const writes = [
457
+ writeFile(join(chaptersDir, filename), chapterContent, "utf-8"),
458
+ writeFile(join(storyDir, "current_state.md"), runtimeStateArtifacts?.currentStateMarkdown ?? output.updatedState, "utf-8"),
459
+ writeFile(join(storyDir, "pending_hooks.md"), runtimeStateArtifacts?.hooksMarkdown ?? output.updatedHooks, "utf-8"),
460
+ ];
461
+ if (runtimeStateArtifacts?.chapterSummariesMarkdown) {
462
+ writes.push(writeFile(join(storyDir, "chapter_summaries.md"), runtimeStateArtifacts.chapterSummariesMarkdown, "utf-8"));
463
+ }
464
+ if (runtimeStateArtifacts?.snapshot ?? output.runtimeStateSnapshot) {
465
+ writes.push(saveRuntimeStateSnapshot(bookDir, runtimeStateArtifacts?.snapshot ?? output.runtimeStateSnapshot));
466
+ }
467
+ if (numericalSystem) {
468
+ writes.push(writeFile(join(storyDir, "particle_ledger.md"), output.updatedLedger, "utf-8"));
469
+ }
470
+ await Promise.all(writes);
471
+ }
472
+ buildUserPrompt(params) {
473
+ const contextBlock = params.externalContext
474
+ ? `\n## 外部指令\n以下是来自外部系统的创作指令,请在本章中融入:\n\n${params.externalContext}\n`
475
+ : "";
476
+ const ledgerBlock = params.ledger
477
+ ? `\n## 资源账本\n${params.ledger}\n`
478
+ : "";
479
+ const summariesBlock = params.chapterSummaries !== "(文件尚未创建)"
480
+ ? `\n## 章节摘要(全部历史章节压缩上下文)\n${params.chapterSummaries}\n`
481
+ : "";
482
+ const subplotBlock = params.subplotBoard !== "(文件尚未创建)"
483
+ ? `\n## 支线进度板\n${params.subplotBoard}\n`
484
+ : "";
485
+ const emotionalBlock = params.emotionalArcs !== "(文件尚未创建)"
486
+ ? `\n## 情感弧线\n${params.emotionalArcs}\n`
487
+ : "";
488
+ const matrixBlock = params.characterMatrix !== "(文件尚未创建)"
489
+ ? `\n## 角色交互矩阵\n${params.characterMatrix}\n`
490
+ : "";
491
+ const fingerprintBlock = params.dialogueFingerprints
492
+ ? `\n## 角色对话指纹\n${params.dialogueFingerprints}\n`
493
+ : "";
494
+ const relevantBlock = params.relevantSummaries
495
+ ? `\n## 相关历史章节摘要\n${params.relevantSummaries}\n`
496
+ : "";
497
+ const canonBlock = params.parentCanon
498
+ ? `\n## 正传正典参照(番外写作专用)
499
+ 本书是番外作品。以下正典约束不可违反,角色不得引用超出其信息边界的信息。
500
+ ${params.parentCanon}\n`
501
+ : "";
502
+ const lengthRequirementBlock = this.buildLengthRequirementBlock(params.lengthSpec, params.language ?? "zh");
503
+ if (params.language === "en") {
504
+ return `Write chapter ${params.chapterNumber}.
505
+ ${contextBlock}
506
+ ## Current State
507
+ ${params.currentState}
508
+ ${ledgerBlock}
509
+ ## Plot Threads
510
+ ${params.hooks}
511
+ ${summariesBlock}${subplotBlock}${emotionalBlock}${matrixBlock}${fingerprintBlock}${relevantBlock}${canonBlock}
512
+ ## Recent Chapters
513
+ ${params.recentChapters || "(This is the first chapter, no previous text)"}
514
+
515
+ ## Worldbuilding
516
+ ${params.storyBible}
517
+
518
+ ## Volume Outline (Hard Constraint — Must Follow)
519
+ ${params.volumeOutline}
520
+
521
+ [Outline Rules]
522
+ - This chapter must advance the plot points assigned to it in the volume outline. Do not skip ahead or consume future plot points.
523
+ - If the outline specifies an event for chapter N, do not resolve it early.
524
+ - Pacing must match the outline's chapter span: if 5 chapters are planned for an arc, do not compress into 1-2.
525
+ - PRE_WRITE_CHECK must identify which outline node this chapter covers.
526
+
527
+ ${lengthRequirementBlock}
528
+ - Output PRE_WRITE_CHECK first, then the chapter
529
+ - Output only PRE_WRITE_CHECK, CHAPTER_TITLE, and CHAPTER_CONTENT blocks`;
530
+ }
531
+ return `请续写第${params.chapterNumber}章。
532
+ ${contextBlock}
533
+ ## 当前状态卡
534
+ ${params.currentState}
535
+ ${ledgerBlock}
536
+ ## 伏笔池
537
+ ${params.hooks}
538
+ ${summariesBlock}${subplotBlock}${emotionalBlock}${matrixBlock}${fingerprintBlock}${relevantBlock}${canonBlock}
539
+ ## 最近章节
540
+ ${params.recentChapters || "(这是第一章,无前文)"}
541
+
542
+ ## 世界观设定
543
+ ${params.storyBible}
544
+
545
+ ## 卷纲(硬约束——必须遵守)
546
+ ${params.volumeOutline}
547
+
548
+ 【卷纲遵守规则】
549
+ - 本章内容必须对应卷纲中当前章节范围内的剧情节点,严禁跳过或提前消耗后续节点
550
+ - 如果卷纲指定了某个事件/转折发生在第N章,不得提前到本章完成
551
+ - 剧情推进速度必须与卷纲规划的章节跨度匹配:如果卷纲规划某段剧情跨5章,不得在1-2章内讲完
552
+ - PRE_WRITE_CHECK中必须明确标注本章对应的卷纲节点
553
+
554
+ ${lengthRequirementBlock}
555
+ - 先输出写作自检表,再写正文
556
+ - 只需输出 PRE_WRITE_CHECK、CHAPTER_TITLE、CHAPTER_CONTENT 三个区块`;
557
+ }
558
+ buildGovernedUserPrompt(params) {
559
+ const contextSections = params.contextPackage.selectedContext
560
+ .map((entry) => [
561
+ `### ${entry.source}`,
562
+ `- reason: ${entry.reason}`,
563
+ entry.excerpt ? `- excerpt: ${entry.excerpt}` : "",
564
+ ].filter(Boolean).join("\n"))
565
+ .join("\n\n");
566
+ const overrideLines = params.ruleStack.activeOverrides.length > 0
567
+ ? params.ruleStack.activeOverrides
568
+ .map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
569
+ .join("\n")
570
+ : "- none";
571
+ const diagnosticLines = params.ruleStack.sections.diagnostic.length > 0
572
+ ? params.ruleStack.sections.diagnostic.join(", ")
573
+ : "none";
574
+ const traceNotes = params.trace && params.trace.notes.length > 0
575
+ ? params.trace.notes.map((note) => `- ${note}`).join("\n")
576
+ : "- none";
577
+ const lengthRequirementBlock = this.buildLengthRequirementBlock(params.lengthSpec, params.language ?? "zh");
578
+ const varianceBlock = params.varianceBrief
579
+ ? `\n${params.varianceBrief}\n`
580
+ : "";
581
+ const selectedEvidenceBlock = params.selectedEvidenceBlock
582
+ ? `\n${params.selectedEvidenceBlock}\n`
583
+ : "";
584
+ const explicitHookAgenda = this.extractMarkdownSection(params.chapterIntent, "## Hook Agenda");
585
+ const hookAgendaBlock = explicitHookAgenda
586
+ ? params.language === "en"
587
+ ? `\n## Explicit Hook Agenda\n${explicitHookAgenda}\n`
588
+ : `\n## 显式 Hook Agenda\n${explicitHookAgenda}\n`
589
+ : "";
590
+ if (params.language === "en") {
591
+ return `Write chapter ${params.chapterNumber}.
592
+
593
+ ## Chapter Intent
594
+ ${params.chapterIntent}
595
+
596
+ ## Selected Context
597
+ ${contextSections || "(none)"}
598
+ ${selectedEvidenceBlock}
599
+ ${hookAgendaBlock}
600
+
601
+ ## Rule Stack
602
+ - Hard: ${params.ruleStack.sections.hard.join(", ") || "(none)"}
603
+ - Soft: ${params.ruleStack.sections.soft.join(", ") || "(none)"}
604
+ - Diagnostic: ${diagnosticLines}
605
+
606
+ ## Active Overrides
607
+ ${overrideLines}
608
+
609
+ ## Trace Notes
610
+ ${traceNotes}
611
+
612
+ ${varianceBlock}
613
+ ${lengthRequirementBlock}
614
+ - Output PRE_WRITE_CHECK first, then the chapter
615
+ - Output only PRE_WRITE_CHECK, CHAPTER_TITLE, and CHAPTER_CONTENT blocks`;
616
+ }
617
+ return `请续写第${params.chapterNumber}章。
618
+
619
+ ## 本章意图
620
+ ${params.chapterIntent}
621
+
622
+ ## 已选上下文
623
+ ${contextSections || "(无)"}
624
+ ${selectedEvidenceBlock}
625
+ ${hookAgendaBlock}
626
+
627
+ ## 规则栈
628
+ - 硬护栏:${params.ruleStack.sections.hard.join("、") || "(无)"}
629
+ - 软约束:${params.ruleStack.sections.soft.join("、") || "(无)"}
630
+ - 诊断规则:${diagnosticLines}
631
+
632
+ ## 当前覆盖
633
+ ${overrideLines}
634
+
635
+ ## 追踪说明
636
+ ${traceNotes}
637
+
638
+ ${varianceBlock}
639
+ ${lengthRequirementBlock}
640
+ - 先输出写作自检表,再写正文
641
+ - 只需输出 PRE_WRITE_CHECK、CHAPTER_TITLE、CHAPTER_CONTENT 三个区块`;
642
+ }
643
+ joinGovernedEvidenceBlocks(blocks) {
644
+ if (!blocks) {
645
+ return undefined;
646
+ }
647
+ const joined = [
648
+ blocks.titleHistoryBlock,
649
+ blocks.moodTrailBlock,
650
+ blocks.canonBlock,
651
+ blocks.hookDebtBlock,
652
+ blocks.hooksBlock,
653
+ blocks.summariesBlock,
654
+ blocks.volumeSummariesBlock,
655
+ ]
656
+ .filter((block) => Boolean(block))
657
+ .join("\n");
658
+ return joined || undefined;
659
+ }
660
+ extractMarkdownSection(content, heading) {
661
+ const lines = content.split("\n");
662
+ let buffer = null;
663
+ for (const line of lines) {
664
+ if (line.trim() === heading) {
665
+ buffer = [];
666
+ continue;
667
+ }
668
+ if (buffer && line.startsWith("## ") && line.trim() !== heading) {
669
+ break;
670
+ }
671
+ if (buffer) {
672
+ buffer.push(line);
673
+ }
674
+ }
675
+ const section = buffer?.join("\n").trim();
676
+ return section && section.length > 0 ? section : undefined;
677
+ }
678
+ buildSettlerGovernedControlBlock(chapterIntent, contextPackage, ruleStack, language) {
679
+ const selectedContext = contextPackage.selectedContext
680
+ .map((entry) => `- ${entry.source}: ${entry.reason}${entry.excerpt ? ` | ${entry.excerpt}` : ""}`)
681
+ .join("\n");
682
+ const overrides = ruleStack.activeOverrides.length > 0
683
+ ? ruleStack.activeOverrides
684
+ .map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
685
+ .join("\n")
686
+ : "- none";
687
+ if (language === "en") {
688
+ return `\n## Chapter Control Inputs
689
+ ${chapterIntent}
690
+
691
+ ### Selected Context
692
+ ${selectedContext || "- none"}
693
+
694
+ ### Rule Stack
695
+ - Hard guardrails: ${ruleStack.sections.hard.join(", ") || "(none)"}
696
+ - Soft constraints: ${ruleStack.sections.soft.join(", ") || "(none)"}
697
+ - Diagnostic rules: ${ruleStack.sections.diagnostic.join(", ") || "(none)"}
698
+
699
+ ### Active Overrides
700
+ ${overrides}\n`;
701
+ }
702
+ return `\n## 本章控制输入
703
+ ${chapterIntent}
704
+
705
+ ### 已选上下文
706
+ ${selectedContext || "- none"}
707
+
708
+ ### 规则栈
709
+ - 硬护栏:${ruleStack.sections.hard.join("、") || "(无)"}
710
+ - 软约束:${ruleStack.sections.soft.join("、") || "(无)"}
711
+ - 诊断规则:${ruleStack.sections.diagnostic.join("、") || "(无)"}
712
+
713
+ ### 当前覆盖
714
+ ${overrides}\n`;
715
+ }
716
+ buildLengthRequirementBlock(lengthSpec, language) {
717
+ if (language === "en") {
718
+ return `Requirements:
719
+ - Target length: ${lengthSpec.target} words
720
+ - Acceptable range: ${lengthSpec.softMin}-${lengthSpec.softMax} words`;
721
+ }
722
+ return `要求:
723
+ - 目标字数:${lengthSpec.target}字
724
+ - 允许区间:${lengthSpec.softMin}-${lengthSpec.softMax}字`;
725
+ }
726
+ async loadRecentChapters(bookDir, currentChapter, count = 1) {
727
+ const chaptersDir = join(bookDir, "chapters");
728
+ try {
729
+ const files = await readdir(chaptersDir);
730
+ const mdFiles = files
731
+ .filter((f) => f.endsWith(".md") && !f.startsWith("index"))
732
+ .sort()
733
+ .slice(-count);
734
+ if (mdFiles.length === 0)
735
+ return "";
736
+ const contents = await Promise.all(mdFiles.map(async (f) => {
737
+ const content = await readFile(join(chaptersDir, f), "utf-8");
738
+ return content;
739
+ }));
740
+ return contents.join("\n\n---\n\n");
741
+ }
742
+ catch {
743
+ return "";
744
+ }
745
+ }
746
+ async readFileOrDefault(path) {
747
+ try {
748
+ return await readFile(path, "utf-8");
749
+ }
750
+ catch {
751
+ return "(文件尚未创建)";
752
+ }
753
+ }
754
+ /** Save new truth files (summaries, subplots, emotional arcs, character matrix). */
755
+ async saveNewTruthFiles(bookDir, output, language = "zh") {
756
+ const storyDir = join(bookDir, "story");
757
+ const writes = [];
758
+ // Append chapter summary to chapter_summaries.md
759
+ if (!output.runtimeStateDelta && output.updatedChapterSummaries) {
760
+ writes.push(writeFile(join(storyDir, "chapter_summaries.md"), output.updatedChapterSummaries, "utf-8"));
761
+ }
762
+ else if (!output.runtimeStateDelta && output.chapterSummary) {
763
+ writes.push(this.appendChapterSummary(storyDir, output.chapterSummary, language));
764
+ }
765
+ // Overwrite subplot board
766
+ if (output.updatedSubplots) {
767
+ writes.push(writeFile(join(storyDir, "subplot_board.md"), output.updatedSubplots, "utf-8"));
768
+ }
769
+ // Overwrite emotional arcs
770
+ if (output.updatedEmotionalArcs) {
771
+ writes.push(writeFile(join(storyDir, "emotional_arcs.md"), output.updatedEmotionalArcs, "utf-8"));
772
+ }
773
+ // Overwrite character matrix
774
+ if (output.updatedCharacterMatrix) {
775
+ writes.push(writeFile(join(storyDir, "character_matrix.md"), output.updatedCharacterMatrix, "utf-8"));
776
+ }
777
+ await Promise.all(writes);
778
+ }
779
+ renderDeltaSummaryRow(delta) {
780
+ if (!delta.chapterSummary)
781
+ return "";
782
+ const summary = delta.chapterSummary;
783
+ const row = [
784
+ summary.chapter,
785
+ summary.title,
786
+ summary.characters,
787
+ summary.events,
788
+ summary.stateChanges,
789
+ summary.hookActivity,
790
+ summary.mood,
791
+ summary.chapterType,
792
+ ].map((value) => String(value).replace(/\|/g, "\\|").trim()).join(" | ");
793
+ return `| ${row} |`;
794
+ }
795
+ normalizeRuntimeStateDeltaChapter(delta, authoritativeChapterNumber) {
796
+ const hookOps = delta.hookOps ?? {
797
+ upsert: [],
798
+ mention: [],
799
+ resolve: [],
800
+ defer: [],
801
+ };
802
+ let changed = delta.chapter !== authoritativeChapterNumber;
803
+ const normalizedUpserts = hookOps.upsert.map((hook) => {
804
+ const startChapter = Math.min(hook.startChapter, authoritativeChapterNumber);
805
+ const lastAdvancedChapter = Math.min(hook.lastAdvancedChapter, authoritativeChapterNumber);
806
+ if (startChapter !== hook.startChapter || lastAdvancedChapter !== hook.lastAdvancedChapter) {
807
+ changed = true;
808
+ }
809
+ if (startChapter === hook.startChapter && lastAdvancedChapter === hook.lastAdvancedChapter) {
810
+ return hook;
811
+ }
812
+ return {
813
+ ...hook,
814
+ startChapter,
815
+ lastAdvancedChapter,
816
+ };
817
+ });
818
+ if (delta.chapterSummary?.chapter !== undefined && delta.chapterSummary.chapter !== authoritativeChapterNumber) {
819
+ changed = true;
820
+ }
821
+ if (!changed) {
822
+ return delta;
823
+ }
824
+ return {
825
+ ...delta,
826
+ chapter: authoritativeChapterNumber,
827
+ hookOps: {
828
+ ...hookOps,
829
+ upsert: normalizedUpserts,
830
+ },
831
+ chapterSummary: delta.chapterSummary
832
+ ? {
833
+ ...delta.chapterSummary,
834
+ chapter: authoritativeChapterNumber,
835
+ }
836
+ : undefined,
837
+ };
838
+ }
839
+ async buildRuntimeStateArtifactsIfPresent(bookDir, delta, language, authoritativeChapterNumber, allowReapply) {
840
+ if (!delta)
841
+ return null;
842
+ const safeDelta = authoritativeChapterNumber === undefined
843
+ ? delta
844
+ : this.normalizeRuntimeStateDeltaChapter(delta, authoritativeChapterNumber);
845
+ return buildRuntimeStateArtifacts({
846
+ bookDir,
847
+ delta: safeDelta,
848
+ language,
849
+ allowReapply,
850
+ });
851
+ }
852
+ async resolveRuntimeStateArtifactsForOutput(bookDir, output, language) {
853
+ if (!output.runtimeStateDelta)
854
+ return null;
855
+ const safeDelta = this.normalizeRuntimeStateDeltaChapter(output.runtimeStateDelta, output.chapterNumber);
856
+ if (safeDelta === output.runtimeStateDelta
857
+ && output.runtimeStateSnapshot
858
+ && output.updatedChapterSummaries
859
+ && output.updatedState
860
+ && output.updatedHooks) {
861
+ return {
862
+ snapshot: output.runtimeStateSnapshot,
863
+ resolvedDelta: safeDelta,
864
+ currentStateMarkdown: output.updatedState,
865
+ hooksMarkdown: output.updatedHooks,
866
+ chapterSummariesMarkdown: output.updatedChapterSummaries,
867
+ };
868
+ }
869
+ return buildRuntimeStateArtifacts({
870
+ bookDir,
871
+ delta: safeDelta,
872
+ language,
873
+ });
874
+ }
875
+ async appendChapterSummary(storyDir, summary, language) {
876
+ const summaryPath = join(storyDir, "chapter_summaries.md");
877
+ let existing = "";
878
+ try {
879
+ existing = await readFile(summaryPath, "utf-8");
880
+ }
881
+ catch {
882
+ // File doesn't exist yet — start with header
883
+ existing = language === "en"
884
+ ? "# Chapter Summaries\n\n| Chapter | Title | Characters | Key Events | State Changes | Hook Activity | Mood | Chapter Type |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n"
885
+ : "# 章节摘要\n\n| 章节 | 标题 | 出场人物 | 关键事件 | 状态变化 | 伏笔动态 | 情绪基调 | 章节类型 |\n|------|------|----------|----------|----------|----------|----------|----------|\n";
886
+ }
887
+ // Extract only the data row(s) from the summary (skip header lines)
888
+ const dataRows = summary
889
+ .split("\n")
890
+ .filter((line) => line.startsWith("|")
891
+ && !line.startsWith("| 章节")
892
+ && !line.startsWith("| Chapter")
893
+ && !line.startsWith("|--")
894
+ && !line.startsWith("| ---"))
895
+ .join("\n");
896
+ if (dataRows) {
897
+ // Deduplicate: remove existing rows with the same chapter number before appending
898
+ const newChapterNums = new Set(dataRows.split("\n")
899
+ .map((line) => line.split("|")[1]?.trim())
900
+ .filter((ch) => ch && /^\d+$/.test(ch)));
901
+ const deduped = existing
902
+ .split("\n")
903
+ .filter((line) => {
904
+ if (!line.startsWith("|"))
905
+ return true;
906
+ const chNum = line.split("|")[1]?.trim();
907
+ return !chNum || !newChapterNums.has(chNum);
908
+ })
909
+ .join("\n");
910
+ await writeFile(summaryPath, `${deduped.trimEnd()}\n${dataRows}\n`, "utf-8");
911
+ }
912
+ }
913
+ buildStyleFingerprint(styleProfileRaw) {
914
+ if (!styleProfileRaw || styleProfileRaw === "(文件尚未创建)")
915
+ return undefined;
916
+ try {
917
+ const profile = JSON.parse(styleProfileRaw);
918
+ const lines = [];
919
+ if (profile.avgSentenceLength)
920
+ lines.push(`- 平均句长:${profile.avgSentenceLength}字`);
921
+ if (profile.sentenceLengthStdDev)
922
+ lines.push(`- 句长标准差:${profile.sentenceLengthStdDev}`);
923
+ if (profile.avgParagraphLength)
924
+ lines.push(`- 平均段落长度:${profile.avgParagraphLength}字`);
925
+ if (profile.paragraphLengthRange)
926
+ lines.push(`- 段落长度范围:${profile.paragraphLengthRange.min}-${profile.paragraphLengthRange.max}字`);
927
+ if (profile.vocabularyDiversity)
928
+ lines.push(`- 词汇多样性(TTR):${profile.vocabularyDiversity}`);
929
+ if (profile.topPatterns?.length > 0)
930
+ lines.push(`- 高频句式:${profile.topPatterns.join("、")}`);
931
+ if (profile.rhetoricalFeatures?.length > 0)
932
+ lines.push(`- 修辞特征:${profile.rhetoricalFeatures.join("、")}`);
933
+ return lines.length > 0 ? lines.join("\n") : undefined;
934
+ }
935
+ catch {
936
+ return undefined;
937
+ }
938
+ }
939
+ /**
940
+ * Extract dialogue fingerprints from recent chapters.
941
+ * For each character with multiple dialogue lines, compute speaking style markers.
942
+ */
943
+ extractDialogueFingerprints(recentChapters, _storyBible) {
944
+ if (!recentChapters)
945
+ return "";
946
+ // Match dialogue patterns:
947
+ // Chinese: "speaker说道:" or dialogue in ""「」
948
+ // English: "dialogue," speaker said. or "dialogue."
949
+ const dialogueRegex = /(?:(.{1,6})(?:说道|道|喝道|冷声道|笑道|怒道|低声道|大声道|喝骂道|冷笑道|沉声道|喊道|叫道|问道|答道)\s*[::]\s*["""「]([^"""」]+)["""」])|["""「]([^"""」]{2,})["""」]|"([^"]{2,})"/g;
950
+ const characterDialogues = new Map();
951
+ let match;
952
+ while ((match = dialogueRegex.exec(recentChapters)) !== null) {
953
+ const speaker = match[1]?.trim();
954
+ const line = match[2] ?? match[3] ?? "";
955
+ if (speaker && line.length > 1) {
956
+ const existing = characterDialogues.get(speaker) ?? [];
957
+ characterDialogues.set(speaker, [...existing, line]);
958
+ }
959
+ }
960
+ // Only include characters with >=2 dialogue lines
961
+ const fingerprints = [];
962
+ for (const [character, lines] of characterDialogues) {
963
+ if (lines.length < 2)
964
+ continue;
965
+ const avgLen = Math.round(lines.reduce((sum, l) => sum + l.length, 0) / lines.length);
966
+ const isShort = avgLen < 15;
967
+ // Find frequent words/phrases (2+ occurrences)
968
+ const wordCounts = new Map();
969
+ for (const line of lines) {
970
+ // Extract 2-3 char segments as "words"
971
+ for (let i = 0; i < line.length - 1; i++) {
972
+ const bigram = line.slice(i, i + 2);
973
+ wordCounts.set(bigram, (wordCounts.get(bigram) ?? 0) + 1);
974
+ }
975
+ }
976
+ const frequentWords = [...wordCounts.entries()]
977
+ .filter(([, count]) => count >= 2)
978
+ .sort((a, b) => b[1] - a[1])
979
+ .slice(0, 3)
980
+ .map(([w]) => `「${w}」`);
981
+ // Detect style markers
982
+ const markers = [];
983
+ if (isShort)
984
+ markers.push("短句为主");
985
+ else
986
+ markers.push("长句为主");
987
+ const questionCount = lines.filter((l) => l.includes("?") || l.includes("?")).length;
988
+ if (questionCount > lines.length * 0.3)
989
+ markers.push("反问多");
990
+ if (frequentWords.length > 0)
991
+ markers.push(`常用${frequentWords.join("")}`);
992
+ fingerprints.push(`${character}:${markers.join(",")}`);
993
+ }
994
+ return fingerprints.length > 0 ? fingerprints.join(";") : "";
995
+ }
996
+ /**
997
+ * Find relevant chapter summaries based on volume outline context.
998
+ * Extracts character names and hook IDs from the current volume's outline,
999
+ * then searches chapter summaries for matching entries.
1000
+ */
1001
+ findRelevantSummaries(chapterSummaries, volumeOutline, chapterNumber) {
1002
+ if (!chapterSummaries || chapterSummaries === "(文件尚未创建)")
1003
+ return "";
1004
+ if (!volumeOutline || volumeOutline === "(文件尚未创建)")
1005
+ return "";
1006
+ // Extract character names from volume outline (Chinese name patterns)
1007
+ const nameRegex = /[\u4e00-\u9fff]{2,4}(?=[,、。:]|$)/g;
1008
+ const outlineNames = new Set();
1009
+ let nameMatch;
1010
+ while ((nameMatch = nameRegex.exec(volumeOutline)) !== null) {
1011
+ outlineNames.add(nameMatch[0]);
1012
+ }
1013
+ // Extract hook IDs from volume outline
1014
+ const hookRegex = /H\d{2,}/g;
1015
+ const hookIds = new Set();
1016
+ let hookMatch;
1017
+ while ((hookMatch = hookRegex.exec(volumeOutline)) !== null) {
1018
+ hookIds.add(hookMatch[0]);
1019
+ }
1020
+ if (outlineNames.size === 0 && hookIds.size === 0)
1021
+ return "";
1022
+ // Search chapter summaries for matching rows
1023
+ const rows = chapterSummaries.split("\n").filter((line) => line.startsWith("|") && !line.startsWith("| 章节") && !line.startsWith("|--") && !line.startsWith("| -"));
1024
+ const matchedRows = rows.filter((row) => {
1025
+ for (const name of outlineNames) {
1026
+ if (row.includes(name))
1027
+ return true;
1028
+ }
1029
+ for (const hookId of hookIds) {
1030
+ if (row.includes(hookId))
1031
+ return true;
1032
+ }
1033
+ return false;
1034
+ });
1035
+ // Skip only the last chapter (its full text is already in context via loadRecentChapters)
1036
+ const filteredRows = matchedRows.filter((row) => {
1037
+ const chNumMatch = row.match(/\|\s*(\d+)\s*\|/);
1038
+ if (!chNumMatch)
1039
+ return true;
1040
+ const num = parseInt(chNumMatch[1], 10);
1041
+ return num < chapterNumber - 1;
1042
+ });
1043
+ return filteredRows.length > 0 ? filteredRows.join("\n") : "";
1044
+ }
1045
+ sanitizeFilename(title) {
1046
+ return title
1047
+ .replace(/[/\\?%*:|"<>]/g, "")
1048
+ .replace(/\s+/g, "_")
1049
+ .slice(0, 50);
1050
+ }
1051
+ }
1052
+ //# sourceMappingURL=writer.js.map