@zwbigi/ink-xy 0.1.2 → 0.1.4

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 (598) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -2
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/server/app/_global-error.html +1 -1
  5. package/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found.html +1 -1
  13. package/.next/server/app/_not-found.rsc +1 -1
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  20. package/.next/server/app/api/inkos/route.js.nft.json +1 -1
  21. package/.next/server/app/api/skills/install/route.js.nft.json +1 -1
  22. package/.next/server/app/api/skills/search/route.js.nft.json +1 -1
  23. package/.next/server/app/index.html +1 -1
  24. package/.next/server/app/index.rsc +1 -1
  25. package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  26. package/.next/server/app/index.segments/_full.segment.rsc +1 -1
  27. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  28. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  29. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  30. package/.next/server/app-paths-manifest.json +2 -2
  31. package/.next/server/chunks/162.js +1 -1
  32. package/.next/server/middleware-build-manifest.js +1 -1
  33. package/.next/server/pages/404.html +1 -1
  34. package/.next/server/pages/500.html +1 -1
  35. package/.next/trace +4 -4
  36. package/.next/trace-build +1 -1
  37. package/bin/pi-web.js +4 -1
  38. package/inkos/.env.example +20 -0
  39. package/inkos/.node-version +1 -0
  40. package/inkos/.nvmrc +1 -0
  41. package/inkos/CHANGELOG.md +787 -0
  42. package/inkos/CONTRIBUTING.md +89 -0
  43. package/inkos/LICENSE +661 -0
  44. package/inkos/README.en.md +483 -0
  45. package/inkos/README.ja.md +461 -0
  46. package/inkos/README.md +272 -0
  47. package/inkos/assets/15qun.jpg +0 -0
  48. package/inkos/assets/41777702961_.pic.jpg +0 -0
  49. package/inkos/assets/inkos-short-demo-cover.png +0 -0
  50. package/inkos/assets/inkos-text.svg +40 -0
  51. package/inkos/assets/logo.svg +47 -0
  52. package/inkos/assets/screenshot-chapters.png +0 -0
  53. package/inkos/assets/screenshot-pipeline.png +0 -0
  54. package/inkos/assets/screenshot-state.png +0 -0
  55. package/inkos/assets/screenshot-terminal.png +0 -0
  56. package/inkos/assets/wechat-group-v8.jpg +0 -0
  57. package/inkos/package.json +42 -0
  58. package/inkos/packages/cli/package.json +74 -0
  59. package/inkos/packages/cli/src/__tests__/analytics.test.ts +154 -0
  60. package/inkos/packages/cli/src/__tests__/cli-integration.test.ts +1031 -0
  61. package/inkos/packages/cli/src/__tests__/daemon.test.ts +93 -0
  62. package/inkos/packages/cli/src/__tests__/doctor.test.ts +36 -0
  63. package/inkos/packages/cli/src/__tests__/interact-command.test.ts +142 -0
  64. package/inkos/packages/cli/src/__tests__/interaction-tools.test.ts +107 -0
  65. package/inkos/packages/cli/src/__tests__/llm-overrides.test.ts +25 -0
  66. package/inkos/packages/cli/src/__tests__/localization.test.ts +121 -0
  67. package/inkos/packages/cli/src/__tests__/progress-text.test.ts +92 -0
  68. package/inkos/packages/cli/src/__tests__/project-bootstrap.test.ts +71 -0
  69. package/inkos/packages/cli/src/__tests__/publish-package.test.ts +272 -0
  70. package/inkos/packages/cli/src/__tests__/revision-command.test.ts +82 -0
  71. package/inkos/packages/cli/src/__tests__/runtime-requirements.test.ts +89 -0
  72. package/inkos/packages/cli/src/__tests__/short-fiction-command.test.ts +48 -0
  73. package/inkos/packages/cli/src/__tests__/studio-runtime.test.ts +142 -0
  74. package/inkos/packages/cli/src/__tests__/studio.test.ts +87 -0
  75. package/inkos/packages/cli/src/__tests__/tui-activity-state.test.ts +20 -0
  76. package/inkos/packages/cli/src/__tests__/tui-agent-session.test.ts +213 -0
  77. package/inkos/packages/cli/src/__tests__/tui-chat-depth.test.ts +27 -0
  78. package/inkos/packages/cli/src/__tests__/tui-chat-draft.test.ts +44 -0
  79. package/inkos/packages/cli/src/__tests__/tui-command.test.ts +86 -0
  80. package/inkos/packages/cli/src/__tests__/tui-composer-caret.test.ts +46 -0
  81. package/inkos/packages/cli/src/__tests__/tui-composer-display.test.ts +40 -0
  82. package/inkos/packages/cli/src/__tests__/tui-dashboard.test.tsx +219 -0
  83. package/inkos/packages/cli/src/__tests__/tui-effects-i18n.test.ts +29 -0
  84. package/inkos/packages/cli/src/__tests__/tui-i18n.test.ts +22 -0
  85. package/inkos/packages/cli/src/__tests__/tui-input-chrome.test.ts +10 -0
  86. package/inkos/packages/cli/src/__tests__/tui-input-history.test.ts +40 -0
  87. package/inkos/packages/cli/src/__tests__/tui-layout.test.ts +55 -0
  88. package/inkos/packages/cli/src/__tests__/tui-local-commands.test.ts +47 -0
  89. package/inkos/packages/cli/src/__tests__/tui-session-store.test.ts +81 -0
  90. package/inkos/packages/cli/src/__tests__/tui-setup-i18n.test.ts +31 -0
  91. package/inkos/packages/cli/src/__tests__/tui-slash-autocomplete.test.ts +33 -0
  92. package/inkos/packages/cli/src/commands/agent.ts +65 -0
  93. package/inkos/packages/cli/src/commands/analytics.ts +77 -0
  94. package/inkos/packages/cli/src/commands/audit.ts +52 -0
  95. package/inkos/packages/cli/src/commands/book.ts +260 -0
  96. package/inkos/packages/cli/src/commands/compose.ts +50 -0
  97. package/inkos/packages/cli/src/commands/config.ts +328 -0
  98. package/inkos/packages/cli/src/commands/consolidate.ts +50 -0
  99. package/inkos/packages/cli/src/commands/daemon.ts +121 -0
  100. package/inkos/packages/cli/src/commands/detect.ts +125 -0
  101. package/inkos/packages/cli/src/commands/doctor.ts +391 -0
  102. package/inkos/packages/cli/src/commands/draft.ts +43 -0
  103. package/inkos/packages/cli/src/commands/eval.ts +217 -0
  104. package/inkos/packages/cli/src/commands/export.ts +45 -0
  105. package/inkos/packages/cli/src/commands/fanfic.ts +183 -0
  106. package/inkos/packages/cli/src/commands/genre.ts +160 -0
  107. package/inkos/packages/cli/src/commands/import.ts +158 -0
  108. package/inkos/packages/cli/src/commands/init.ts +47 -0
  109. package/inkos/packages/cli/src/commands/interact.ts +109 -0
  110. package/inkos/packages/cli/src/commands/plan.ts +54 -0
  111. package/inkos/packages/cli/src/commands/radar.ts +60 -0
  112. package/inkos/packages/cli/src/commands/review.ts +253 -0
  113. package/inkos/packages/cli/src/commands/revise.ts +58 -0
  114. package/inkos/packages/cli/src/commands/short-fiction.ts +294 -0
  115. package/inkos/packages/cli/src/commands/status.ts +138 -0
  116. package/inkos/packages/cli/src/commands/studio.ts +194 -0
  117. package/inkos/packages/cli/src/commands/style.ts +99 -0
  118. package/inkos/packages/cli/src/commands/tui.ts +18 -0
  119. package/inkos/packages/cli/src/commands/update.ts +45 -0
  120. package/inkos/packages/cli/src/commands/write.ts +324 -0
  121. package/inkos/packages/cli/src/index.ts +5 -0
  122. package/inkos/packages/cli/src/interaction/tools.ts +49 -0
  123. package/inkos/packages/cli/src/localization.ts +215 -0
  124. package/inkos/packages/cli/src/program.ts +106 -0
  125. package/inkos/packages/cli/src/progress-text.ts +85 -0
  126. package/inkos/packages/cli/src/project-bootstrap.ts +175 -0
  127. package/inkos/packages/cli/src/runtime-requirements.ts +135 -0
  128. package/inkos/packages/cli/src/tui/__tests__/markdown.test.ts +64 -0
  129. package/inkos/packages/cli/src/tui/activity-state.ts +41 -0
  130. package/inkos/packages/cli/src/tui/agent-input.ts +264 -0
  131. package/inkos/packages/cli/src/tui/ansi.ts +72 -0
  132. package/inkos/packages/cli/src/tui/app.ts +130 -0
  133. package/inkos/packages/cli/src/tui/chat-depth.ts +22 -0
  134. package/inkos/packages/cli/src/tui/chat-draft.ts +42 -0
  135. package/inkos/packages/cli/src/tui/composer-caret.ts +22 -0
  136. package/inkos/packages/cli/src/tui/composer-display.ts +26 -0
  137. package/inkos/packages/cli/src/tui/dashboard-model.ts +164 -0
  138. package/inkos/packages/cli/src/tui/dashboard.tsx +544 -0
  139. package/inkos/packages/cli/src/tui/effects.ts +542 -0
  140. package/inkos/packages/cli/src/tui/i18n.ts +278 -0
  141. package/inkos/packages/cli/src/tui/input-history.ts +69 -0
  142. package/inkos/packages/cli/src/tui/local-commands.ts +55 -0
  143. package/inkos/packages/cli/src/tui/markdown.ts +64 -0
  144. package/inkos/packages/cli/src/tui/session-store.ts +6 -0
  145. package/inkos/packages/cli/src/tui/setup.ts +397 -0
  146. package/inkos/packages/cli/src/tui/slash-autocomplete.ts +62 -0
  147. package/inkos/packages/cli/src/tui/theme.ts +17 -0
  148. package/inkos/packages/cli/src/utils.ts +222 -0
  149. package/inkos/packages/cli/tsconfig.json +9 -0
  150. package/inkos/packages/core/genres/cozy.md +43 -0
  151. package/inkos/packages/core/genres/cultivation.md +42 -0
  152. package/inkos/packages/core/genres/dungeon-core.md +40 -0
  153. package/inkos/packages/core/genres/horror.md +51 -0
  154. package/inkos/packages/core/genres/isekai.md +43 -0
  155. package/inkos/packages/core/genres/litrpg.md +43 -0
  156. package/inkos/packages/core/genres/other.md +24 -0
  157. package/inkos/packages/core/genres/progression.md +41 -0
  158. package/inkos/packages/core/genres/romantasy.md +45 -0
  159. package/inkos/packages/core/genres/sci-fi.md +42 -0
  160. package/inkos/packages/core/genres/system-apocalypse.md +40 -0
  161. package/inkos/packages/core/genres/tower-climber.md +41 -0
  162. package/inkos/packages/core/genres/urban.md +53 -0
  163. package/inkos/packages/core/genres/xianxia.md +46 -0
  164. package/inkos/packages/core/genres/xuanhuan.md +64 -0
  165. package/inkos/packages/core/package.json +61 -0
  166. package/inkos/packages/core/src/__tests__/agent-max-tokens-policy.test.ts +29 -0
  167. package/inkos/packages/core/src/__tests__/agent-session.test.ts +866 -0
  168. package/inkos/packages/core/src/__tests__/agent-system-prompt.test.ts +167 -0
  169. package/inkos/packages/core/src/__tests__/agent-tools-params.test.ts +197 -0
  170. package/inkos/packages/core/src/__tests__/agent-tools.test.ts +421 -0
  171. package/inkos/packages/core/src/__tests__/ai-tells.test.ts +90 -0
  172. package/inkos/packages/core/src/__tests__/architect-phase5-consolidated.test.ts +445 -0
  173. package/inkos/packages/core/src/__tests__/architect-phase5.test.ts +455 -0
  174. package/inkos/packages/core/src/__tests__/architect-phase7.test.ts +210 -0
  175. package/inkos/packages/core/src/__tests__/architect.test.ts +859 -0
  176. package/inkos/packages/core/src/__tests__/audit-parse.test.ts +78 -0
  177. package/inkos/packages/core/src/__tests__/book-id.test.ts +26 -0
  178. package/inkos/packages/core/src/__tests__/book-session-store.test.ts +447 -0
  179. package/inkos/packages/core/src/__tests__/book-session.test.ts +113 -0
  180. package/inkos/packages/core/src/__tests__/chapter-analyzer.test.ts +574 -0
  181. package/inkos/packages/core/src/__tests__/chapter-memo-parser.test.ts +247 -0
  182. package/inkos/packages/core/src/__tests__/chapter-persistence.test.ts +198 -0
  183. package/inkos/packages/core/src/__tests__/chapter-review-cycle.test.ts +294 -0
  184. package/inkos/packages/core/src/__tests__/chapter-splitter.test.ts +156 -0
  185. package/inkos/packages/core/src/__tests__/chapter-state-recovery.test.ts +235 -0
  186. package/inkos/packages/core/src/__tests__/chapter-truth-validation.test.ts +253 -0
  187. package/inkos/packages/core/src/__tests__/composer.test.ts +627 -0
  188. package/inkos/packages/core/src/__tests__/config-loader.test.ts +325 -0
  189. package/inkos/packages/core/src/__tests__/config-migration.test.ts +102 -0
  190. package/inkos/packages/core/src/__tests__/consolidator.test.ts +32 -0
  191. package/inkos/packages/core/src/__tests__/context-filter.test.ts +60 -0
  192. package/inkos/packages/core/src/__tests__/context-transform.test.ts +108 -0
  193. package/inkos/packages/core/src/__tests__/continuity.test.ts +391 -0
  194. package/inkos/packages/core/src/__tests__/detection-insights.test.ts +59 -0
  195. package/inkos/packages/core/src/__tests__/detector.test.ts +86 -0
  196. package/inkos/packages/core/src/__tests__/draft-directive-parser.test.ts +386 -0
  197. package/inkos/packages/core/src/__tests__/edit-controller.test.ts +190 -0
  198. package/inkos/packages/core/src/__tests__/effective-llm-config.test.ts +486 -0
  199. package/inkos/packages/core/src/__tests__/fanfic-dimensions.test.ts +58 -0
  200. package/inkos/packages/core/src/__tests__/fanfic-models.test.ts +69 -0
  201. package/inkos/packages/core/src/__tests__/governed-working-set.test.ts +155 -0
  202. package/inkos/packages/core/src/__tests__/hook-arbiter.test.ts +124 -0
  203. package/inkos/packages/core/src/__tests__/hook-governance.test.ts +228 -0
  204. package/inkos/packages/core/src/__tests__/hook-health.test.ts +166 -0
  205. package/inkos/packages/core/src/__tests__/hook-ledger-validator.test.ts +236 -0
  206. package/inkos/packages/core/src/__tests__/hook-promotion.test.ts +192 -0
  207. package/inkos/packages/core/src/__tests__/hook-stale-detection.test.ts +136 -0
  208. package/inkos/packages/core/src/__tests__/index-notify-lazy.test.ts +20 -0
  209. package/inkos/packages/core/src/__tests__/interaction-chat-tokens.test.ts +170 -0
  210. package/inkos/packages/core/src/__tests__/interaction-models.test.ts +155 -0
  211. package/inkos/packages/core/src/__tests__/interaction-nl-router.test.ts +223 -0
  212. package/inkos/packages/core/src/__tests__/interaction-runtime.test.ts +633 -0
  213. package/inkos/packages/core/src/__tests__/interaction-tools.test.ts +343 -0
  214. package/inkos/packages/core/src/__tests__/length-metrics.test.ts +82 -0
  215. package/inkos/packages/core/src/__tests__/length-normalizer.test.ts +331 -0
  216. package/inkos/packages/core/src/__tests__/list-models.test.ts +109 -0
  217. package/inkos/packages/core/src/__tests__/llm-env.test.ts +31 -0
  218. package/inkos/packages/core/src/__tests__/logger.test.ts +175 -0
  219. package/inkos/packages/core/src/__tests__/long-span-fatigue.test.ts +160 -0
  220. package/inkos/packages/core/src/__tests__/memory-retrieval.test.ts +1303 -0
  221. package/inkos/packages/core/src/__tests__/models.test.ts +918 -0
  222. package/inkos/packages/core/src/__tests__/outline-paths.test.ts +97 -0
  223. package/inkos/packages/core/src/__tests__/path-safety.test.ts +22 -0
  224. package/inkos/packages/core/src/__tests__/persisted-governed-plan.test.ts +134 -0
  225. package/inkos/packages/core/src/__tests__/phase5-cleanup.test.ts +393 -0
  226. package/inkos/packages/core/src/__tests__/phase5-hotfix.test.ts +288 -0
  227. package/inkos/packages/core/src/__tests__/phase7-hotfix.test.ts +614 -0
  228. package/inkos/packages/core/src/__tests__/pipeline-agent.test.ts +354 -0
  229. package/inkos/packages/core/src/__tests__/pipeline-runner-memory-sync.test.ts +317 -0
  230. package/inkos/packages/core/src/__tests__/pipeline-runner.test.ts +5200 -0
  231. package/inkos/packages/core/src/__tests__/planner-context.test.ts +137 -0
  232. package/inkos/packages/core/src/__tests__/planner-prompts-ratio.test.ts +11 -0
  233. package/inkos/packages/core/src/__tests__/planner-prompts.test.ts +171 -0
  234. package/inkos/packages/core/src/__tests__/planner.test.ts +362 -0
  235. package/inkos/packages/core/src/__tests__/planning-materials.test.ts +90 -0
  236. package/inkos/packages/core/src/__tests__/polisher.test.ts +189 -0
  237. package/inkos/packages/core/src/__tests__/post-write-validator.test.ts +291 -0
  238. package/inkos/packages/core/src/__tests__/probe.test.ts +77 -0
  239. package/inkos/packages/core/src/__tests__/project-interaction.test.ts +241 -0
  240. package/inkos/packages/core/src/__tests__/provider.test.ts +953 -0
  241. package/inkos/packages/core/src/__tests__/providers-group.test.ts +34 -0
  242. package/inkos/packages/core/src/__tests__/providers-lookup.test.ts +81 -0
  243. package/inkos/packages/core/src/__tests__/providers-schema.test.ts +158 -0
  244. package/inkos/packages/core/src/__tests__/proxy-fetch.test.ts +75 -0
  245. package/inkos/packages/core/src/__tests__/revise-foundation.test.ts +514 -0
  246. package/inkos/packages/core/src/__tests__/reviser.test.ts +859 -0
  247. package/inkos/packages/core/src/__tests__/runtime-state-store.test.ts +388 -0
  248. package/inkos/packages/core/src/__tests__/scheduler.test.ts +123 -0
  249. package/inkos/packages/core/src/__tests__/secrets-migration.test.ts +71 -0
  250. package/inkos/packages/core/src/__tests__/secrets.test.ts +95 -0
  251. package/inkos/packages/core/src/__tests__/sensitive-words.test.ts +88 -0
  252. package/inkos/packages/core/src/__tests__/service-presets-regression.test.ts +73 -0
  253. package/inkos/packages/core/src/__tests__/service-resolver-regression.test.ts +75 -0
  254. package/inkos/packages/core/src/__tests__/service-resolver.test.ts +228 -0
  255. package/inkos/packages/core/src/__tests__/session-transcript-restore.test.ts +1311 -0
  256. package/inkos/packages/core/src/__tests__/session-transcript.test.ts +195 -0
  257. package/inkos/packages/core/src/__tests__/settler-delta-parser.test.ts +133 -0
  258. package/inkos/packages/core/src/__tests__/short-fiction-public.test.ts +241 -0
  259. package/inkos/packages/core/src/__tests__/spot-fix-patches.test.ts +104 -0
  260. package/inkos/packages/core/src/__tests__/state-manager.test.ts +1298 -0
  261. package/inkos/packages/core/src/__tests__/state-projections.test.ts +130 -0
  262. package/inkos/packages/core/src/__tests__/state-reducer.test.ts +372 -0
  263. package/inkos/packages/core/src/__tests__/state-validator-agent.test.ts +165 -0
  264. package/inkos/packages/core/src/__tests__/state-validator.test.ts +122 -0
  265. package/inkos/packages/core/src/__tests__/style-analyzer.test.ts +61 -0
  266. package/inkos/packages/core/src/__tests__/temperature-constraints.test.ts +57 -0
  267. package/inkos/packages/core/src/__tests__/v13-hotfix-round4.test.ts +343 -0
  268. package/inkos/packages/core/src/__tests__/verify-service.test.ts +77 -0
  269. package/inkos/packages/core/src/__tests__/webhook.test.ts +91 -0
  270. package/inkos/packages/core/src/__tests__/writer-parser.test.ts +348 -0
  271. package/inkos/packages/core/src/__tests__/writer-prompts.test.ts +269 -0
  272. package/inkos/packages/core/src/__tests__/writer.test.ts +1360 -0
  273. package/inkos/packages/core/src/agent/agent-session.ts +737 -0
  274. package/inkos/packages/core/src/agent/agent-system-prompt.ts +199 -0
  275. package/inkos/packages/core/src/agent/agent-tools.ts +835 -0
  276. package/inkos/packages/core/src/agent/context-transform.ts +85 -0
  277. package/inkos/packages/core/src/agent/index.ts +14 -0
  278. package/inkos/packages/core/src/agents/ai-tells.ts +161 -0
  279. package/inkos/packages/core/src/agents/architect.ts +1291 -0
  280. package/inkos/packages/core/src/agents/base.ts +100 -0
  281. package/inkos/packages/core/src/agents/chapter-analyzer.ts +634 -0
  282. package/inkos/packages/core/src/agents/composer.ts +469 -0
  283. package/inkos/packages/core/src/agents/consolidator.ts +218 -0
  284. package/inkos/packages/core/src/agents/continuity.ts +824 -0
  285. package/inkos/packages/core/src/agents/detection-insights.ts +72 -0
  286. package/inkos/packages/core/src/agents/detector.ts +224 -0
  287. package/inkos/packages/core/src/agents/en-prompt-sections.ts +129 -0
  288. package/inkos/packages/core/src/agents/fanfic-canon-importer.ts +146 -0
  289. package/inkos/packages/core/src/agents/fanfic-dimensions.ts +87 -0
  290. package/inkos/packages/core/src/agents/fanfic-prompt-sections.ts +109 -0
  291. package/inkos/packages/core/src/agents/foundation-reviewer.ts +204 -0
  292. package/inkos/packages/core/src/agents/length-normalizer.ts +218 -0
  293. package/inkos/packages/core/src/agents/observer-prompts.ts +127 -0
  294. package/inkos/packages/core/src/agents/planner-context.ts +297 -0
  295. package/inkos/packages/core/src/agents/planner-prompts.ts +404 -0
  296. package/inkos/packages/core/src/agents/planner.ts +783 -0
  297. package/inkos/packages/core/src/agents/polisher.ts +153 -0
  298. package/inkos/packages/core/src/agents/post-write-validator.ts +873 -0
  299. package/inkos/packages/core/src/agents/radar-source.ts +123 -0
  300. package/inkos/packages/core/src/agents/radar.ts +120 -0
  301. package/inkos/packages/core/src/agents/reviser.ts +701 -0
  302. package/inkos/packages/core/src/agents/rules-reader.ts +155 -0
  303. package/inkos/packages/core/src/agents/sensitive-words.ts +142 -0
  304. package/inkos/packages/core/src/agents/settler-delta-parser.ts +53 -0
  305. package/inkos/packages/core/src/agents/settler-parser.ts +38 -0
  306. package/inkos/packages/core/src/agents/settler-prompts.ts +230 -0
  307. package/inkos/packages/core/src/agents/short-fiction.ts +429 -0
  308. package/inkos/packages/core/src/agents/state-validator.ts +322 -0
  309. package/inkos/packages/core/src/agents/style-analyzer.ts +93 -0
  310. package/inkos/packages/core/src/agents/writer-parser.ts +178 -0
  311. package/inkos/packages/core/src/agents/writer-prompts.ts +899 -0
  312. package/inkos/packages/core/src/agents/writer.ts +1450 -0
  313. package/inkos/packages/core/src/index.ts +392 -0
  314. package/inkos/packages/core/src/interaction/book-session-store.ts +226 -0
  315. package/inkos/packages/core/src/interaction/draft-directive-parser.ts +266 -0
  316. package/inkos/packages/core/src/interaction/edit-controller.ts +270 -0
  317. package/inkos/packages/core/src/interaction/events.ts +41 -0
  318. package/inkos/packages/core/src/interaction/export-artifact.ts +151 -0
  319. package/inkos/packages/core/src/interaction/intents.ts +63 -0
  320. package/inkos/packages/core/src/interaction/modes.ts +13 -0
  321. package/inkos/packages/core/src/interaction/nl-router.ts +258 -0
  322. package/inkos/packages/core/src/interaction/project-control.ts +150 -0
  323. package/inkos/packages/core/src/interaction/project-session-store.ts +81 -0
  324. package/inkos/packages/core/src/interaction/project-tools.ts +704 -0
  325. package/inkos/packages/core/src/interaction/request-router.ts +5 -0
  326. package/inkos/packages/core/src/interaction/runtime.ts +1167 -0
  327. package/inkos/packages/core/src/interaction/session-transcript-legacy.ts +113 -0
  328. package/inkos/packages/core/src/interaction/session-transcript-restore.ts +607 -0
  329. package/inkos/packages/core/src/interaction/session-transcript-schema.ts +76 -0
  330. package/inkos/packages/core/src/interaction/session-transcript.ts +189 -0
  331. package/inkos/packages/core/src/interaction/session.ts +226 -0
  332. package/inkos/packages/core/src/interaction/truth-authority.ts +45 -0
  333. package/inkos/packages/core/src/llm/config-migration.ts +58 -0
  334. package/inkos/packages/core/src/llm/cover-providers.ts +45 -0
  335. package/inkos/packages/core/src/llm/provider.ts +1331 -0
  336. package/inkos/packages/core/src/llm/providers/endpoints/ai360.ts +42 -0
  337. package/inkos/packages/core/src/llm/providers/endpoints/anthropic.ts +82 -0
  338. package/inkos/packages/core/src/llm/providers/endpoints/astronCodingPlan.ts +30 -0
  339. package/inkos/packages/core/src/llm/providers/endpoints/baichuan.ts +28 -0
  340. package/inkos/packages/core/src/llm/providers/endpoints/bailian.ts +65 -0
  341. package/inkos/packages/core/src/llm/providers/endpoints/bailianCodingPlan.ts +30 -0
  342. package/inkos/packages/core/src/llm/providers/endpoints/custom.ts +22 -0
  343. package/inkos/packages/core/src/llm/providers/endpoints/deepseek.ts +35 -0
  344. package/inkos/packages/core/src/llm/providers/endpoints/giteeai.ts +41 -0
  345. package/inkos/packages/core/src/llm/providers/endpoints/githubCopilot.ts +43 -0
  346. package/inkos/packages/core/src/llm/providers/endpoints/glmCodingPlan.ts +28 -0
  347. package/inkos/packages/core/src/llm/providers/endpoints/google.ts +51 -0
  348. package/inkos/packages/core/src/llm/providers/endpoints/hunyuan.ts +42 -0
  349. package/inkos/packages/core/src/llm/providers/endpoints/infiniai.ts +72 -0
  350. package/inkos/packages/core/src/llm/providers/endpoints/internlm.ts +28 -0
  351. package/inkos/packages/core/src/llm/providers/endpoints/kimiCode.ts +23 -0
  352. package/inkos/packages/core/src/llm/providers/endpoints/kimiCodingPlan.ts +24 -0
  353. package/inkos/packages/core/src/llm/providers/endpoints/kkaiapi.ts +56 -0
  354. package/inkos/packages/core/src/llm/providers/endpoints/longcat.ts +25 -0
  355. package/inkos/packages/core/src/llm/providers/endpoints/minimax.ts +39 -0
  356. package/inkos/packages/core/src/llm/providers/endpoints/minimaxCodingPlan.ts +28 -0
  357. package/inkos/packages/core/src/llm/providers/endpoints/mistral.ts +40 -0
  358. package/inkos/packages/core/src/llm/providers/endpoints/modelscope.ts +30 -0
  359. package/inkos/packages/core/src/llm/providers/endpoints/moonshot.ts +39 -0
  360. package/inkos/packages/core/src/llm/providers/endpoints/newapi.ts +21 -0
  361. package/inkos/packages/core/src/llm/providers/endpoints/ollama.ts +73 -0
  362. package/inkos/packages/core/src/llm/providers/endpoints/openai.ts +77 -0
  363. package/inkos/packages/core/src/llm/providers/endpoints/opencodeCodingPlan.ts +30 -0
  364. package/inkos/packages/core/src/llm/providers/endpoints/openrouter.ts +87 -0
  365. package/inkos/packages/core/src/llm/providers/endpoints/ppio.ts +86 -0
  366. package/inkos/packages/core/src/llm/providers/endpoints/qiniu.ts +32 -0
  367. package/inkos/packages/core/src/llm/providers/endpoints/sensenova.ts +45 -0
  368. package/inkos/packages/core/src/llm/providers/endpoints/siliconcloud.ts +126 -0
  369. package/inkos/packages/core/src/llm/providers/endpoints/spark.ts +33 -0
  370. package/inkos/packages/core/src/llm/providers/endpoints/stepfun.ts +35 -0
  371. package/inkos/packages/core/src/llm/providers/endpoints/tencentcloud.ts +25 -0
  372. package/inkos/packages/core/src/llm/providers/endpoints/volcengine.ts +52 -0
  373. package/inkos/packages/core/src/llm/providers/endpoints/volcengineCodingPlan.ts +42 -0
  374. package/inkos/packages/core/src/llm/providers/endpoints/wenxin.ts +106 -0
  375. package/inkos/packages/core/src/llm/providers/endpoints/xai.ts +34 -0
  376. package/inkos/packages/core/src/llm/providers/endpoints/xiaomimimo.ts +26 -0
  377. package/inkos/packages/core/src/llm/providers/endpoints/zeroone.ts +34 -0
  378. package/inkos/packages/core/src/llm/providers/endpoints/zhipu.ts +61 -0
  379. package/inkos/packages/core/src/llm/providers/index.ts +71 -0
  380. package/inkos/packages/core/src/llm/providers/lookup.ts +70 -0
  381. package/inkos/packages/core/src/llm/providers/probe.ts +35 -0
  382. package/inkos/packages/core/src/llm/providers/types.ts +89 -0
  383. package/inkos/packages/core/src/llm/providers/verify.ts +104 -0
  384. package/inkos/packages/core/src/llm/secrets.ts +77 -0
  385. package/inkos/packages/core/src/llm/service-presets.ts +215 -0
  386. package/inkos/packages/core/src/llm/service-resolver.ts +91 -0
  387. package/inkos/packages/core/src/models/book-rules.ts +126 -0
  388. package/inkos/packages/core/src/models/book.ts +70 -0
  389. package/inkos/packages/core/src/models/chapter.ts +42 -0
  390. package/inkos/packages/core/src/models/detection.ts +25 -0
  391. package/inkos/packages/core/src/models/genre-profile.ts +36 -0
  392. package/inkos/packages/core/src/models/input-governance.ts +99 -0
  393. package/inkos/packages/core/src/models/length-governance.ts +46 -0
  394. package/inkos/packages/core/src/models/project.ts +161 -0
  395. package/inkos/packages/core/src/models/runtime-state.ts +144 -0
  396. package/inkos/packages/core/src/models/state.ts +52 -0
  397. package/inkos/packages/core/src/models/style-profile.ts +15 -0
  398. package/inkos/packages/core/src/notify/dispatcher.ts +96 -0
  399. package/inkos/packages/core/src/notify/feishu.ts +34 -0
  400. package/inkos/packages/core/src/notify/telegram.ts +25 -0
  401. package/inkos/packages/core/src/notify/webhook.ts +58 -0
  402. package/inkos/packages/core/src/notify/wechat-work.ts +22 -0
  403. package/inkos/packages/core/src/pipeline/agent.ts +691 -0
  404. package/inkos/packages/core/src/pipeline/chapter-persistence.ts +79 -0
  405. package/inkos/packages/core/src/pipeline/chapter-review-cycle.ts +324 -0
  406. package/inkos/packages/core/src/pipeline/chapter-state-recovery.ts +236 -0
  407. package/inkos/packages/core/src/pipeline/chapter-truth-validation.ts +145 -0
  408. package/inkos/packages/core/src/pipeline/detection-runner.ts +164 -0
  409. package/inkos/packages/core/src/pipeline/persisted-governed-plan.ts +216 -0
  410. package/inkos/packages/core/src/pipeline/runner.ts +3438 -0
  411. package/inkos/packages/core/src/pipeline/scheduler.ts +411 -0
  412. package/inkos/packages/core/src/pipeline/short-fiction-runner.ts +801 -0
  413. package/inkos/packages/core/src/prompts/index.ts +1 -0
  414. package/inkos/packages/core/src/prompts/short-fiction.ts +273 -0
  415. package/inkos/packages/core/src/state/manager.ts +560 -0
  416. package/inkos/packages/core/src/state/memory-db.ts +359 -0
  417. package/inkos/packages/core/src/state/runtime-state-store.ts +164 -0
  418. package/inkos/packages/core/src/state/state-bootstrap.ts +657 -0
  419. package/inkos/packages/core/src/state/state-projections.ts +255 -0
  420. package/inkos/packages/core/src/state/state-reducer.ts +260 -0
  421. package/inkos/packages/core/src/state/state-validator.ts +117 -0
  422. package/inkos/packages/core/src/utils/analytics.ts +92 -0
  423. package/inkos/packages/core/src/utils/book-id.ts +31 -0
  424. package/inkos/packages/core/src/utils/cadence-policy.ts +46 -0
  425. package/inkos/packages/core/src/utils/chapter-cadence.ts +211 -0
  426. package/inkos/packages/core/src/utils/chapter-memo-parser.ts +157 -0
  427. package/inkos/packages/core/src/utils/chapter-splitter.ts +80 -0
  428. package/inkos/packages/core/src/utils/config-loader.ts +29 -0
  429. package/inkos/packages/core/src/utils/context-assembly.ts +98 -0
  430. package/inkos/packages/core/src/utils/context-filter.ts +190 -0
  431. package/inkos/packages/core/src/utils/effective-llm-config.ts +529 -0
  432. package/inkos/packages/core/src/utils/governed-context.ts +101 -0
  433. package/inkos/packages/core/src/utils/governed-working-set.ts +395 -0
  434. package/inkos/packages/core/src/utils/hook-arbiter.ts +332 -0
  435. package/inkos/packages/core/src/utils/hook-governance.ts +199 -0
  436. package/inkos/packages/core/src/utils/hook-health.ts +189 -0
  437. package/inkos/packages/core/src/utils/hook-ledger-validator.ts +277 -0
  438. package/inkos/packages/core/src/utils/hook-lifecycle.ts +224 -0
  439. package/inkos/packages/core/src/utils/hook-policy.ts +115 -0
  440. package/inkos/packages/core/src/utils/hook-promotion.ts +313 -0
  441. package/inkos/packages/core/src/utils/hook-stale-detection.ts +168 -0
  442. package/inkos/packages/core/src/utils/length-metrics.ts +123 -0
  443. package/inkos/packages/core/src/utils/llm-endpoint-auth.ts +40 -0
  444. package/inkos/packages/core/src/utils/llm-env.ts +74 -0
  445. package/inkos/packages/core/src/utils/logger.ts +123 -0
  446. package/inkos/packages/core/src/utils/long-span-fatigue.ts +545 -0
  447. package/inkos/packages/core/src/utils/memory-retrieval.ts +527 -0
  448. package/inkos/packages/core/src/utils/narrative-control.ts +177 -0
  449. package/inkos/packages/core/src/utils/outline-paths.ts +275 -0
  450. package/inkos/packages/core/src/utils/path-safety.ts +11 -0
  451. package/inkos/packages/core/src/utils/planning-materials.ts +185 -0
  452. package/inkos/packages/core/src/utils/pov-filter.ts +149 -0
  453. package/inkos/packages/core/src/utils/proxy-fetch.ts +44 -0
  454. package/inkos/packages/core/src/utils/runtime-writer.ts +41 -0
  455. package/inkos/packages/core/src/utils/spot-fix-patches.ts +189 -0
  456. package/inkos/packages/core/src/utils/story-markdown.ts +346 -0
  457. package/inkos/packages/core/src/utils/web-search.ts +82 -0
  458. package/inkos/packages/core/src/utils/writing-methodology.ts +164 -0
  459. package/inkos/packages/core/tsconfig.json +8 -0
  460. package/inkos/packages/core/vitest.config.ts +7 -0
  461. package/inkos/packages/studio/components.json +25 -0
  462. package/inkos/packages/studio/index.html +13 -0
  463. package/inkos/packages/studio/package.json +72 -0
  464. package/inkos/packages/studio/postcss.config.js +3 -0
  465. package/inkos/packages/studio/src/App.test.ts +25 -0
  466. package/inkos/packages/studio/src/App.tsx +280 -0
  467. package/inkos/packages/studio/src/api/__tests__/normalize-base-url.test.ts +40 -0
  468. package/inkos/packages/studio/src/api/book-create.test.ts +104 -0
  469. package/inkos/packages/studio/src/api/book-create.ts +94 -0
  470. package/inkos/packages/studio/src/api/errors.ts +17 -0
  471. package/inkos/packages/studio/src/api/index.ts +30 -0
  472. package/inkos/packages/studio/src/api/lib/run-store.ts +177 -0
  473. package/inkos/packages/studio/src/api/lib/sse.ts +50 -0
  474. package/inkos/packages/studio/src/api/phase5-hotfix.test.ts +335 -0
  475. package/inkos/packages/studio/src/api/safety.ts +6 -0
  476. package/inkos/packages/studio/src/api/server.test.ts +3162 -0
  477. package/inkos/packages/studio/src/api/server.ts +3666 -0
  478. package/inkos/packages/studio/src/api/v13-hotfix-round4.test.ts +226 -0
  479. package/inkos/packages/studio/src/app-state.test.ts +8 -0
  480. package/inkos/packages/studio/src/app-state.ts +1 -0
  481. package/inkos/packages/studio/src/components/ConfirmDialog.tsx +95 -0
  482. package/inkos/packages/studio/src/components/ServiceConfigSourceCard.tsx +139 -0
  483. package/inkos/packages/studio/src/components/ServiceQuickLinks.tsx +65 -0
  484. package/inkos/packages/studio/src/components/Sidebar.tsx +652 -0
  485. package/inkos/packages/studio/src/components/ai-elements/code-block.tsx +562 -0
  486. package/inkos/packages/studio/src/components/ai-elements/confirmation.tsx +174 -0
  487. package/inkos/packages/studio/src/components/ai-elements/message.tsx +360 -0
  488. package/inkos/packages/studio/src/components/ai-elements/prompt-input.tsx +1457 -0
  489. package/inkos/packages/studio/src/components/ai-elements/reasoning.tsx +226 -0
  490. package/inkos/packages/studio/src/components/ai-elements/shimmer.tsx +77 -0
  491. package/inkos/packages/studio/src/components/ai-elements/tool.tsx +173 -0
  492. package/inkos/packages/studio/src/components/chat/BookSidebar.tsx +291 -0
  493. package/inkos/packages/studio/src/components/chat/ChatMessage.tsx +39 -0
  494. package/inkos/packages/studio/src/components/chat/QuickActions.tsx +73 -0
  495. package/inkos/packages/studio/src/components/chat/ToolExecutionSteps.tsx +320 -0
  496. package/inkos/packages/studio/src/components/chat/__tests__/ToolExecutionSteps.test.ts +114 -0
  497. package/inkos/packages/studio/src/components/chat-utils.ts +56 -0
  498. package/inkos/packages/studio/src/components/chatbar-state.test.ts +69 -0
  499. package/inkos/packages/studio/src/components/sidebar/ChaptersSection.tsx +66 -0
  500. package/inkos/packages/studio/src/components/sidebar/CharacterSection.tsx +129 -0
  501. package/inkos/packages/studio/src/components/sidebar/FoundationSection.tsx +61 -0
  502. package/inkos/packages/studio/src/components/sidebar/ProgressSection.tsx +124 -0
  503. package/inkos/packages/studio/src/components/sidebar/SidebarCard.tsx +30 -0
  504. package/inkos/packages/studio/src/components/sidebar/SummarySection.tsx +89 -0
  505. package/inkos/packages/studio/src/components/ui/alert.tsx +76 -0
  506. package/inkos/packages/studio/src/components/ui/badge.tsx +52 -0
  507. package/inkos/packages/studio/src/components/ui/button-group.tsx +87 -0
  508. package/inkos/packages/studio/src/components/ui/button.tsx +58 -0
  509. package/inkos/packages/studio/src/components/ui/collapsible.tsx +19 -0
  510. package/inkos/packages/studio/src/components/ui/command.tsx +194 -0
  511. package/inkos/packages/studio/src/components/ui/dialog.tsx +158 -0
  512. package/inkos/packages/studio/src/components/ui/dropdown-menu.tsx +266 -0
  513. package/inkos/packages/studio/src/components/ui/hover-card.tsx +51 -0
  514. package/inkos/packages/studio/src/components/ui/input-group.tsx +158 -0
  515. package/inkos/packages/studio/src/components/ui/input.tsx +20 -0
  516. package/inkos/packages/studio/src/components/ui/select.tsx +199 -0
  517. package/inkos/packages/studio/src/components/ui/separator.tsx +23 -0
  518. package/inkos/packages/studio/src/components/ui/spinner.tsx +10 -0
  519. package/inkos/packages/studio/src/components/ui/textarea.tsx +18 -0
  520. package/inkos/packages/studio/src/components/ui/tooltip.tsx +66 -0
  521. package/inkos/packages/studio/src/constants/service-groups.ts +29 -0
  522. package/inkos/packages/studio/src/hooks/use-api.test.ts +93 -0
  523. package/inkos/packages/studio/src/hooks/use-api.ts +189 -0
  524. package/inkos/packages/studio/src/hooks/use-book-activity.test.ts +129 -0
  525. package/inkos/packages/studio/src/hooks/use-book-activity.ts +180 -0
  526. package/inkos/packages/studio/src/hooks/use-colors.ts +27 -0
  527. package/inkos/packages/studio/src/hooks/use-hash-route.test.ts +101 -0
  528. package/inkos/packages/studio/src/hooks/use-hash-route.ts +89 -0
  529. package/inkos/packages/studio/src/hooks/use-i18n.ts +289 -0
  530. package/inkos/packages/studio/src/hooks/use-session-events.ts +64 -0
  531. package/inkos/packages/studio/src/hooks/use-sse.test.ts +52 -0
  532. package/inkos/packages/studio/src/hooks/use-sse.ts +92 -0
  533. package/inkos/packages/studio/src/hooks/use-theme.test.ts +31 -0
  534. package/inkos/packages/studio/src/hooks/use-theme.ts +75 -0
  535. package/inkos/packages/studio/src/index.css +323 -0
  536. package/inkos/packages/studio/src/lib/error-copy.test.ts +34 -0
  537. package/inkos/packages/studio/src/lib/error-copy.ts +37 -0
  538. package/inkos/packages/studio/src/lib/utils.ts +6 -0
  539. package/inkos/packages/studio/src/main.tsx +10 -0
  540. package/inkos/packages/studio/src/pages/Analytics.tsx +80 -0
  541. package/inkos/packages/studio/src/pages/BookCreate.tsx +895 -0
  542. package/inkos/packages/studio/src/pages/BookDetail.tsx +652 -0
  543. package/inkos/packages/studio/src/pages/ChapterReader.tsx +266 -0
  544. package/inkos/packages/studio/src/pages/ChatPage.tsx +521 -0
  545. package/inkos/packages/studio/src/pages/DaemonControl.tsx +116 -0
  546. package/inkos/packages/studio/src/pages/Dashboard.tsx +379 -0
  547. package/inkos/packages/studio/src/pages/DoctorView.tsx +82 -0
  548. package/inkos/packages/studio/src/pages/GenreManager.tsx +464 -0
  549. package/inkos/packages/studio/src/pages/ImportManager.tsx +216 -0
  550. package/inkos/packages/studio/src/pages/LanguageSelector.tsx +74 -0
  551. package/inkos/packages/studio/src/pages/LogViewer.tsx +82 -0
  552. package/inkos/packages/studio/src/pages/RadarView.tsx +157 -0
  553. package/inkos/packages/studio/src/pages/ServiceDetailPage.tsx +393 -0
  554. package/inkos/packages/studio/src/pages/ServiceListPage.tsx +463 -0
  555. package/inkos/packages/studio/src/pages/StyleManager.tsx +225 -0
  556. package/inkos/packages/studio/src/pages/TruthFiles.tsx +194 -0
  557. package/inkos/packages/studio/src/pages/chat-page-state.test.ts +206 -0
  558. package/inkos/packages/studio/src/pages/chat-page-state.ts +112 -0
  559. package/inkos/packages/studio/src/pages/page-state.test.ts +258 -0
  560. package/inkos/packages/studio/src/pages/service-detail-state.test.ts +294 -0
  561. package/inkos/packages/studio/src/pages/service-detail-state.ts +234 -0
  562. package/inkos/packages/studio/src/pages/style-manager-state.test.ts +22 -0
  563. package/inkos/packages/studio/src/pages/truth-files-state.test.ts +61 -0
  564. package/inkos/packages/studio/src/shared/contracts.ts +143 -0
  565. package/inkos/packages/studio/src/store/chat/__tests__/message-parts.test.ts +172 -0
  566. package/inkos/packages/studio/src/store/chat/index.ts +3 -0
  567. package/inkos/packages/studio/src/store/chat/initialState.ts +8 -0
  568. package/inkos/packages/studio/src/store/chat/message-policy.test.ts +16 -0
  569. package/inkos/packages/studio/src/store/chat/message-policy.ts +5 -0
  570. package/inkos/packages/studio/src/store/chat/parts-builder.ts +187 -0
  571. package/inkos/packages/studio/src/store/chat/selectors.ts +13 -0
  572. package/inkos/packages/studio/src/store/chat/slices/create/action.ts +10 -0
  573. package/inkos/packages/studio/src/store/chat/slices/create/initialState.ts +9 -0
  574. package/inkos/packages/studio/src/store/chat/slices/message/action.ts +417 -0
  575. package/inkos/packages/studio/src/store/chat/slices/message/initialState.ts +10 -0
  576. package/inkos/packages/studio/src/store/chat/slices/message/runtime.test.ts +21 -0
  577. package/inkos/packages/studio/src/store/chat/slices/message/runtime.ts +233 -0
  578. package/inkos/packages/studio/src/store/chat/slices/message/stream-events.ts +272 -0
  579. package/inkos/packages/studio/src/store/chat/store.ts +11 -0
  580. package/inkos/packages/studio/src/store/chat/types.ts +169 -0
  581. package/inkos/packages/studio/src/store/service/index.ts +2 -0
  582. package/inkos/packages/studio/src/store/service/store.ts +123 -0
  583. package/inkos/packages/studio/src/store/service/types.ts +50 -0
  584. package/inkos/packages/studio/tsconfig.json +24 -0
  585. package/inkos/packages/studio/tsconfig.server.json +11 -0
  586. package/inkos/packages/studio/vite.config.ts +34 -0
  587. package/inkos/packages/studio/vitest.config.ts +14 -0
  588. package/inkos/pnpm-lock.yaml +9569 -0
  589. package/inkos/pnpm-workspace.yaml +2 -0
  590. package/inkos/scripts/prepare-package-for-publish.mjs +135 -0
  591. package/inkos/scripts/restore-package-json.mjs +31 -0
  592. package/inkos/scripts/set-package-versions.mjs +74 -0
  593. package/inkos/scripts/verify-no-workspace-protocol.mjs +140 -0
  594. package/inkos/skills/SKILL.md +654 -0
  595. package/inkos/tsconfig.json +19 -0
  596. package/package.json +4 -3
  597. /package/.next/static/{F2hMZMf1IyCVAWpkbtRz7 → -3vIrBZXdQ0rp7Wa3Kz40}/_buildManifest.js +0 -0
  598. /package/.next/static/{F2hMZMf1IyCVAWpkbtRz7 → -3vIrBZXdQ0rp7Wa3Kz40}/_ssgManifest.js +0 -0
@@ -0,0 +1,1450 @@
1
+ import { BaseAgent } from "./base.js";
2
+ import type { BookConfig } from "../models/book.js";
3
+ import type { GenreProfile } from "../models/genre-profile.js";
4
+ import type { BookRules } from "../models/book-rules.js";
5
+ import { buildWriterSystemPrompt, type FanficContext } from "./writer-prompts.js";
6
+ import { buildSettlerSystemPrompt, buildSettlerUserPrompt } from "./settler-prompts.js";
7
+ import { buildObserverSystemPrompt, buildObserverUserPrompt } from "./observer-prompts.js";
8
+ import { parseSettlerDeltaOutput } from "./settler-delta-parser.js";
9
+ import { parseSettlementOutput } from "./settler-parser.js";
10
+ import { readGenreProfile, readBookRules } from "./rules-reader.js";
11
+ import {
12
+ detectCrossChapterRepetition,
13
+ detectParagraphLengthDrift,
14
+ normalizePostWriteSurface,
15
+ validatePostWrite,
16
+ type PostWriteViolation,
17
+ } from "./post-write-validator.js";
18
+ import { analyzeAITells } from "./ai-tells.js";
19
+ import type { ChapterIntent, ChapterMemo, ContextPackage, RuleStack } from "../models/input-governance.js";
20
+ import type { LengthSpec } from "../models/length-governance.js";
21
+ import type { RuntimeStateDelta } from "../models/runtime-state.js";
22
+ import { buildLengthSpec, countChapterLength } from "../utils/length-metrics.js";
23
+ import {
24
+ capContextBlock,
25
+ filterHooks,
26
+ filterSummaries,
27
+ filterSubplots,
28
+ filterEmotionalArcs,
29
+ filterCharacterMatrix,
30
+ } from "../utils/context-filter.js";
31
+ import { buildGovernedMemoryEvidenceBlocks } from "../utils/governed-context.js";
32
+ import {
33
+ buildGovernedCharacterMatrixWorkingSet,
34
+ buildGovernedHookWorkingSet,
35
+ mergeCharacterMatrixMarkdown,
36
+ mergeTableMarkdownByKey,
37
+ } from "../utils/governed-working-set.js";
38
+ import { extractPOVFromOutline, filterMatrixByPOV, filterHooksByPOV } from "../utils/pov-filter.js";
39
+ import { parseCreativeOutput } from "./writer-parser.js";
40
+ import { buildRuntimeStateArtifacts, saveRuntimeStateSnapshot, type RuntimeStateArtifacts } from "../state/runtime-state-store.js";
41
+ import type { RuntimeStateSnapshot } from "../state/state-reducer.js";
42
+ import { parsePendingHooksMarkdown } from "../utils/memory-retrieval.js";
43
+ import { analyzeHookHealth } from "../utils/hook-health.js";
44
+ import { buildEnglishVarianceBrief } from "../utils/long-span-fatigue.js";
45
+ import {
46
+ buildNarrativeIntentBrief,
47
+ renderMemoAsNarrativeBlock,
48
+ renderNarrativeSelectedContext,
49
+ sanitizeNarrativeEvidenceBlock,
50
+ } from "../utils/narrative-control.js";
51
+ import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
52
+ import { join } from "node:path";
53
+
54
+ const LEGACY_WRITER_CONTEXT_BUDGET = {
55
+ storyBible: 14_000,
56
+ currentState: 7_000,
57
+ ledger: 6_000,
58
+ hooks: 9_000,
59
+ chapterSummaries: 9_000,
60
+ subplotBoard: 7_000,
61
+ emotionalArcs: 7_000,
62
+ characterMatrix: 12_000,
63
+ parentCanon: 12_000,
64
+ volumeOutline: 12_000,
65
+ } as const;
66
+ import {
67
+ readStoryFrame,
68
+ readVolumeMap,
69
+ readCharacterContext,
70
+ readCurrentStateWithFallback,
71
+ } from "../utils/outline-paths.js";
72
+
73
+ export interface WriteChapterInput {
74
+ readonly book: BookConfig;
75
+ readonly bookDir: string;
76
+ readonly chapterNumber: number;
77
+ readonly externalContext?: string;
78
+ readonly chapterIntent?: string;
79
+ readonly chapterMemo?: ChapterMemo;
80
+ readonly chapterIntentData?: ChapterIntent;
81
+ readonly contextPackage?: ContextPackage;
82
+ readonly ruleStack?: RuleStack;
83
+ readonly lengthSpec?: LengthSpec;
84
+ readonly wordCountOverride?: number;
85
+ readonly temperatureOverride?: number;
86
+ }
87
+
88
+ export interface SettleChapterStateInput {
89
+ readonly book: BookConfig;
90
+ readonly bookDir: string;
91
+ readonly chapterNumber: number;
92
+ readonly title: string;
93
+ readonly content: string;
94
+ readonly allowReapply?: boolean;
95
+ readonly chapterIntent?: string;
96
+ readonly contextPackage?: ContextPackage;
97
+ readonly ruleStack?: RuleStack;
98
+ readonly validationFeedback?: string;
99
+ }
100
+
101
+ export interface TokenUsage {
102
+ readonly promptTokens: number;
103
+ readonly completionTokens: number;
104
+ readonly totalTokens: number;
105
+ }
106
+
107
+ export interface WriteChapterOutput {
108
+ readonly chapterNumber: number;
109
+ readonly title: string;
110
+ readonly content: string;
111
+ readonly wordCount: number;
112
+ readonly preWriteCheck: string;
113
+ readonly postSettlement: string;
114
+ readonly runtimeStateDelta?: RuntimeStateDelta;
115
+ readonly runtimeStateSnapshot?: RuntimeStateSnapshot;
116
+ readonly updatedState: string;
117
+ readonly updatedLedger: string;
118
+ readonly updatedHooks: string;
119
+ readonly chapterSummary: string;
120
+ readonly updatedChapterSummaries?: string;
121
+ readonly updatedSubplots: string;
122
+ readonly updatedEmotionalArcs: string;
123
+ readonly updatedCharacterMatrix: string;
124
+ readonly postWriteErrors: ReadonlyArray<PostWriteViolation>;
125
+ readonly postWriteWarnings: ReadonlyArray<PostWriteViolation>;
126
+ readonly hookHealthIssues?: ReadonlyArray<{
127
+ readonly severity: "critical" | "warning" | "info";
128
+ readonly category: string;
129
+ readonly description: string;
130
+ readonly suggestion: string;
131
+ }>;
132
+ readonly tokenUsage?: TokenUsage;
133
+ }
134
+
135
+ export class WriterAgent extends BaseAgent {
136
+ get name(): string {
137
+ return "writer";
138
+ }
139
+
140
+ private localize(language: "zh" | "en", messages: { zh: string; en: string }): string {
141
+ return language === "en" ? messages.en : messages.zh;
142
+ }
143
+
144
+ private logInfo(language: "zh" | "en", messages: { zh: string; en: string }): void {
145
+ this.ctx.logger?.info(this.localize(language, messages));
146
+ }
147
+
148
+ private logWarn(language: "zh" | "en", messages: { zh: string; en: string }): void {
149
+ this.ctx.logger?.warn(this.localize(language, messages));
150
+ }
151
+
152
+ async writeChapter(input: WriteChapterInput): Promise<WriteChapterOutput> {
153
+ const { book, bookDir, chapterNumber } = input;
154
+
155
+ const placeholder = "(文件尚未创建)";
156
+ const [
157
+ storyBible, volumeOutline, styleGuide, currentState, ledger, hooks,
158
+ chapterSummaries, subplotBoard, emotionalArcs, characterMatrix, styleProfileRaw,
159
+ parentCanon, fanficCanonRaw,
160
+ ] = await Promise.all([
161
+ readStoryFrame(bookDir, placeholder),
162
+ readVolumeMap(bookDir, placeholder),
163
+ this.readFileOrDefault(join(bookDir, "story/style_guide.md")),
164
+ // Phase 5 consolidation: architect no longer emits an initial current_state
165
+ // section. When the file is only a seed placeholder, derive initial state
166
+ // from roles/*.Current_State + pending_hooks startChapter=0 rows so the
167
+ // writer still sees substantive content instead of a runtime-append note.
168
+ readCurrentStateWithFallback(bookDir, placeholder),
169
+ this.readFileOrDefault(join(bookDir, "story/particle_ledger.md")),
170
+ this.readFileOrDefault(join(bookDir, "story/pending_hooks.md")),
171
+ this.readFileOrDefault(join(bookDir, "story/chapter_summaries.md")),
172
+ this.readFileOrDefault(join(bookDir, "story/subplot_board.md")),
173
+ this.readFileOrDefault(join(bookDir, "story/emotional_arcs.md")),
174
+ readCharacterContext(bookDir, placeholder),
175
+ this.readFileOrDefault(join(bookDir, "story/style_profile.json")),
176
+ this.readFileOrDefault(join(bookDir, "story/parent_canon.md")),
177
+ this.readFileOrDefault(join(bookDir, "story/fanfic_canon.md")),
178
+ ]);
179
+
180
+ const recentChapters = await this.loadRecentChapters(bookDir, chapterNumber);
181
+ // Load more chapters for dialogue fingerprint extraction (voice consistency over longer span)
182
+ const fingerprintChapters = await this.loadRecentChapters(bookDir, chapterNumber, 5);
183
+
184
+ // Load genre profile + book rules
185
+ const { profile: genreProfile, body: genreBody } =
186
+ await readGenreProfile(this.ctx.projectRoot, book.genre);
187
+ const parsedBookRules = await readBookRules(bookDir);
188
+ const bookRules = parsedBookRules?.rules ?? null;
189
+ const bookRulesBody = parsedBookRules?.body ?? "";
190
+
191
+ const styleFingerprint = this.buildStyleFingerprint(styleProfileRaw);
192
+
193
+ const dialogueFingerprints = this.extractDialogueFingerprints(fingerprintChapters, storyBible);
194
+ const relevantSummaries = this.findRelevantSummaries(chapterSummaries, volumeOutline, chapterNumber);
195
+
196
+ const hasParentCanon = parentCanon !== "(文件尚未创建)";
197
+ const hasFanficCanon = fanficCanonRaw !== "(文件尚未创建)";
198
+ const resolvedLanguage = book.language ?? genreProfile.language;
199
+ const targetWords = input.lengthSpec?.target ?? input.wordCountOverride ?? book.chapterWordCount;
200
+ const resolvedLengthSpec = input.lengthSpec ?? buildLengthSpec(targetWords, resolvedLanguage);
201
+ const governedMemoryBlocks = input.contextPackage
202
+ ? buildGovernedMemoryEvidenceBlocks(input.contextPackage, resolvedLanguage)
203
+ : undefined;
204
+ const englishVarianceBrief = resolvedLanguage === "en"
205
+ ? await buildEnglishVarianceBrief({
206
+ bookDir,
207
+ chapterNumber,
208
+ })
209
+ : null;
210
+
211
+ // Build fanfic context if fanfic_canon.md exists
212
+ const fanficContext: FanficContext | undefined = hasFanficCanon && bookRules?.fanficMode
213
+ ? {
214
+ fanficCanon: fanficCanonRaw,
215
+ fanficMode: bookRules.fanficMode,
216
+ allowedDeviations: bookRules.allowedDeviations ?? [],
217
+ }
218
+ : undefined;
219
+
220
+ // ── Phase 1: Creative writing (temperature 0.7) ──
221
+ const creativeSystemPrompt = buildWriterSystemPrompt(
222
+ book, genreProfile, bookRules, bookRulesBody, genreBody, styleGuide, styleFingerprint,
223
+ chapterNumber, "creative", fanficContext, resolvedLanguage,
224
+ input.chapterMemo ? "governed" : "legacy",
225
+ resolvedLengthSpec,
226
+ );
227
+
228
+ const creativeUserPrompt = input.chapterMemo && input.contextPackage && input.ruleStack
229
+ ? this.buildGovernedUserPrompt({
230
+ chapterNumber,
231
+ chapterMemo: input.chapterMemo,
232
+ chapterIntentData: input.chapterIntentData,
233
+ contextPackage: input.contextPackage,
234
+ ruleStack: input.ruleStack,
235
+ externalContext: input.externalContext,
236
+ lengthSpec: resolvedLengthSpec,
237
+ language: book.language ?? genreProfile.language,
238
+ varianceBrief: englishVarianceBrief?.text,
239
+ selectedEvidenceBlock: this.joinGovernedEvidenceBlocks(governedMemoryBlocks),
240
+ })
241
+ : (() => {
242
+ // Smart context filtering: inject only relevant parts of truth files
243
+ const filteredHooks = filterHooks(hooks);
244
+ const filteredSummaries = filterSummaries(chapterSummaries, chapterNumber);
245
+ const filteredSubplots = filterSubplots(subplotBoard);
246
+ const filteredArcs = filterEmotionalArcs(emotionalArcs, chapterNumber);
247
+ const filteredMatrix = filterCharacterMatrix(characterMatrix, volumeOutline, bookRules?.protagonist?.name);
248
+
249
+ // POV-aware filtering: limit context to what the POV character knows
250
+ const povCharacter = extractPOVFromOutline(volumeOutline, chapterNumber);
251
+ const povFilteredMatrix = povCharacter
252
+ ? filterMatrixByPOV(filteredMatrix, povCharacter)
253
+ : filteredMatrix;
254
+ const povFilteredHooks = povCharacter
255
+ ? filterHooksByPOV(filteredHooks, povCharacter, chapterSummaries)
256
+ : filteredHooks;
257
+
258
+ return this.buildUserPrompt({
259
+ chapterNumber,
260
+ storyBible,
261
+ currentState,
262
+ ledger: genreProfile.numericalSystem ? ledger : "",
263
+ hooks: povFilteredHooks,
264
+ recentChapters,
265
+ lengthSpec: resolvedLengthSpec,
266
+ externalContext: input.externalContext,
267
+ chapterSummaries: filteredSummaries,
268
+ subplotBoard: filteredSubplots,
269
+ emotionalArcs: filteredArcs,
270
+ characterMatrix: povFilteredMatrix,
271
+ dialogueFingerprints,
272
+ relevantSummaries,
273
+ parentCanon: hasParentCanon ? parentCanon : undefined,
274
+ language: book.language ?? genreProfile.language,
275
+ });
276
+ })();
277
+
278
+ const creativeTemperature = input.temperatureOverride ?? 0.7;
279
+
280
+ this.logInfo(resolvedLanguage, {
281
+ zh: `阶段 1:创作正文(第${chapterNumber}章)`,
282
+ en: `Phase 1: creative writing for chapter ${chapterNumber}`,
283
+ });
284
+
285
+ const creativeResponse = await this.chat(
286
+ [
287
+ { role: "system", content: creativeSystemPrompt },
288
+ { role: "user", content: creativeUserPrompt },
289
+ ],
290
+ { temperature: creativeTemperature },
291
+ );
292
+ const creativeUsage = creativeResponse.usage;
293
+
294
+ const creative = parseCreativeOutput(chapterNumber, creativeResponse.content, resolvedLengthSpec.countingMode);
295
+
296
+ // Phase 4: soft-check that PRE_WRITE_CHECK aligns with the chapter memo.
297
+ // Memo was already parse-validated in the planner, so this only warns —
298
+ // the LLM self-check may have skipped or abbreviated a row.
299
+ if (input.chapterMemo) {
300
+ this.verifyPreWriteCheckAlignsWithMemo(creative.preWriteCheck, chapterNumber, resolvedLanguage);
301
+ }
302
+
303
+ // ── Phase 2: State settlement (temperature 0.3) ──
304
+ this.logInfo(resolvedLanguage, {
305
+ zh: `阶段 2:状态结算(第${chapterNumber}章,${creative.wordCount}字)`,
306
+ en: `Phase 2: state settlement for chapter ${chapterNumber} (${creative.wordCount} words)`,
307
+ });
308
+ const isGovernedSettlement = Boolean(input.chapterIntent && input.contextPackage && input.ruleStack);
309
+ const filteredHooksForSettlement = isGovernedSettlement && input.contextPackage
310
+ ? buildGovernedHookWorkingSet({
311
+ hooksMarkdown: hooks,
312
+ contextPackage: input.contextPackage,
313
+ chapterIntent: input.chapterIntent,
314
+ chapterNumber,
315
+ language: resolvedLanguage,
316
+ })
317
+ : hooks;
318
+ const filteredSubplotsForSettlement = isGovernedSettlement
319
+ ? filterSubplots(subplotBoard)
320
+ : subplotBoard;
321
+ const filteredArcsForSettlement = isGovernedSettlement
322
+ ? filterEmotionalArcs(emotionalArcs, chapterNumber)
323
+ : emotionalArcs;
324
+ const filteredMatrixForSettlement = isGovernedSettlement
325
+ ? buildGovernedCharacterMatrixWorkingSet({
326
+ matrixMarkdown: characterMatrix,
327
+ chapterIntent: input.chapterIntent ?? volumeOutline,
328
+ contextPackage: input.contextPackage!,
329
+ protagonistName: bookRules?.protagonist?.name,
330
+ })
331
+ : characterMatrix;
332
+
333
+ const settleResult = await this.settle({
334
+ book,
335
+ genreProfile,
336
+ bookRules,
337
+ chapterNumber,
338
+ title: creative.title,
339
+ content: creative.content,
340
+ currentState,
341
+ ledger: genreProfile.numericalSystem ? ledger : "",
342
+ hooks: filteredHooksForSettlement,
343
+ chapterSummaries: input.contextPackage ? filterSummaries(chapterSummaries, chapterNumber) : chapterSummaries,
344
+ subplotBoard: filteredSubplotsForSettlement,
345
+ emotionalArcs: filteredArcsForSettlement,
346
+ characterMatrix: filteredMatrixForSettlement,
347
+ volumeOutline,
348
+ selectedEvidenceBlock: governedMemoryBlocks
349
+ ? this.joinGovernedEvidenceBlocks(governedMemoryBlocks)
350
+ : undefined,
351
+ chapterIntent: input.chapterIntent,
352
+ contextPackage: input.contextPackage,
353
+ ruleStack: input.ruleStack,
354
+ validationFeedback: undefined,
355
+ originalHooks: hooks,
356
+ originalSubplots: subplotBoard,
357
+ originalEmotionalArcs: emotionalArcs,
358
+ originalCharacterMatrix: characterMatrix,
359
+ });
360
+ const settlement = settleResult.settlement;
361
+ const settleUsage = settleResult.usage;
362
+ const runtimeStateArtifacts = await this.buildRuntimeStateArtifactsIfPresent(
363
+ bookDir,
364
+ settlement.runtimeStateDelta,
365
+ resolvedLanguage,
366
+ chapterNumber,
367
+ );
368
+ const resolvedRuntimeStateDelta = runtimeStateArtifacts?.resolvedDelta ?? settlement.runtimeStateDelta;
369
+ const priorHookIds = new Set(parsePendingHooksMarkdown(hooks).map((hook) => hook.hookId));
370
+ const hookHealthIssues = resolvedRuntimeStateDelta
371
+ && (runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot)
372
+ ? analyzeHookHealth({
373
+ language: resolvedLanguage,
374
+ chapterNumber,
375
+ targetChapters: book.targetChapters,
376
+ hooks: (runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot)!.hooks.hooks,
377
+ delta: resolvedRuntimeStateDelta,
378
+ existingHookIds: [...priorHookIds],
379
+ })
380
+ : [];
381
+
382
+ // ── Post-write validation (regex + rule-based, zero LLM cost) ──
383
+ const surfaceNormalizedContent = normalizePostWriteSurface(creative.content, resolvedLanguage);
384
+ const surfaceNormalizedWordCount = countChapterLength(surfaceNormalizedContent, resolvedLengthSpec.countingMode);
385
+ const ruleViolations = [
386
+ ...validatePostWrite(surfaceNormalizedContent, genreProfile, bookRules, resolvedLanguage),
387
+ ...detectCrossChapterRepetition(surfaceNormalizedContent, fingerprintChapters, resolvedLanguage),
388
+ ...detectParagraphLengthDrift(surfaceNormalizedContent, fingerprintChapters, resolvedLanguage),
389
+ ];
390
+ const aiTellIssues = analyzeAITells(surfaceNormalizedContent, resolvedLanguage).issues;
391
+
392
+ const postWriteErrors = ruleViolations.filter(v => v.severity === "error");
393
+ const postWriteWarnings = ruleViolations.filter(v => v.severity === "warning");
394
+
395
+ if (ruleViolations.length > 0) {
396
+ this.logWarn(resolvedLanguage, {
397
+ zh: `后写校验:第${chapterNumber}章 ${postWriteErrors.length} 个错误,${postWriteWarnings.length} 个警告`,
398
+ en: `Post-write: ${postWriteErrors.length} errors, ${postWriteWarnings.length} warnings in chapter ${chapterNumber}`,
399
+ });
400
+ for (const v of ruleViolations) {
401
+ this.ctx.logger?.warn(`[${v.severity}] ${v.rule}: ${v.description}`);
402
+ }
403
+ }
404
+ if (aiTellIssues.length > 0) {
405
+ this.logWarn(resolvedLanguage, {
406
+ zh: `AI 味检查:第${chapterNumber}章发现 ${aiTellIssues.length} 个问题`,
407
+ en: `AI-tell check: ${aiTellIssues.length} issues in chapter ${chapterNumber}`,
408
+ });
409
+ for (const issue of aiTellIssues) {
410
+ this.ctx.logger?.warn(`[${issue.severity}] ${issue.category}: ${issue.description}`);
411
+ }
412
+ }
413
+ if (hookHealthIssues.length > 0) {
414
+ this.logWarn(resolvedLanguage, {
415
+ zh: `伏笔健康:第${chapterNumber}章发现 ${hookHealthIssues.length} 条警告`,
416
+ en: `Hook health: ${hookHealthIssues.length} warning(s) in chapter ${chapterNumber}`,
417
+ });
418
+ for (const issue of hookHealthIssues) {
419
+ this.ctx.logger?.warn(`[${issue.severity}] ${issue.category}: ${issue.description}`);
420
+ }
421
+ }
422
+
423
+ // ── Merge into WriteChapterOutput ──
424
+ const tokenUsage: TokenUsage = {
425
+ promptTokens: creativeUsage.promptTokens + settleUsage.promptTokens,
426
+ completionTokens: creativeUsage.completionTokens + settleUsage.completionTokens,
427
+ totalTokens: creativeUsage.totalTokens + settleUsage.totalTokens,
428
+ };
429
+
430
+ return {
431
+ chapterNumber,
432
+ title: creative.title,
433
+ content: surfaceNormalizedContent,
434
+ wordCount: surfaceNormalizedWordCount,
435
+ preWriteCheck: creative.preWriteCheck,
436
+ postSettlement: settlement.postSettlement,
437
+ runtimeStateDelta: resolvedRuntimeStateDelta,
438
+ runtimeStateSnapshot: runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot,
439
+ updatedState: runtimeStateArtifacts?.currentStateMarkdown ?? settlement.updatedState,
440
+ updatedLedger: settlement.updatedLedger,
441
+ updatedHooks: runtimeStateArtifacts?.hooksMarkdown ?? settlement.updatedHooks,
442
+ chapterSummary: resolvedRuntimeStateDelta
443
+ ? this.renderDeltaSummaryRow(resolvedRuntimeStateDelta)
444
+ : settlement.chapterSummary,
445
+ updatedChapterSummaries: runtimeStateArtifacts?.chapterSummariesMarkdown,
446
+ updatedSubplots: settlement.updatedSubplots,
447
+ updatedEmotionalArcs: settlement.updatedEmotionalArcs,
448
+ updatedCharacterMatrix: settlement.updatedCharacterMatrix,
449
+ postWriteErrors,
450
+ postWriteWarnings,
451
+ hookHealthIssues,
452
+ tokenUsage,
453
+ };
454
+ }
455
+
456
+ async settleChapterState(input: SettleChapterStateInput): Promise<WriteChapterOutput> {
457
+ const [
458
+ currentState,
459
+ ledger,
460
+ hooks,
461
+ chapterSummaries,
462
+ subplotBoard,
463
+ emotionalArcs,
464
+ characterMatrix,
465
+ volumeOutline,
466
+ ] = await Promise.all([
467
+ // Phase 5 consolidation fallback: derive initial state when only seed on disk.
468
+ readCurrentStateWithFallback(input.bookDir, "(文件尚未创建)"),
469
+ this.readFileOrDefault(join(input.bookDir, "story/particle_ledger.md")),
470
+ this.readFileOrDefault(join(input.bookDir, "story/pending_hooks.md")),
471
+ this.readFileOrDefault(join(input.bookDir, "story/chapter_summaries.md")),
472
+ this.readFileOrDefault(join(input.bookDir, "story/subplot_board.md")),
473
+ this.readFileOrDefault(join(input.bookDir, "story/emotional_arcs.md")),
474
+ readCharacterContext(input.bookDir, "(文件尚未创建)"),
475
+ readVolumeMap(input.bookDir, "(文件尚未创建)"),
476
+ ]);
477
+
478
+ const { profile: genreProfile } = await readGenreProfile(this.ctx.projectRoot, input.book.genre);
479
+ const parsedBookRules = await readBookRules(input.bookDir);
480
+ const bookRules = parsedBookRules?.rules ?? null;
481
+ const resolvedLanguage = input.book.language ?? genreProfile.language;
482
+ const governedMemoryBlocks = input.contextPackage
483
+ ? buildGovernedMemoryEvidenceBlocks(input.contextPackage, resolvedLanguage)
484
+ : undefined;
485
+
486
+ const settleResult = await this.settle({
487
+ book: input.book,
488
+ genreProfile,
489
+ bookRules,
490
+ chapterNumber: input.chapterNumber,
491
+ title: input.title,
492
+ content: input.content,
493
+ currentState,
494
+ ledger: genreProfile.numericalSystem ? ledger : "",
495
+ hooks,
496
+ chapterSummaries,
497
+ subplotBoard,
498
+ emotionalArcs,
499
+ characterMatrix,
500
+ volumeOutline,
501
+ selectedEvidenceBlock: governedMemoryBlocks
502
+ ? this.joinGovernedEvidenceBlocks(governedMemoryBlocks)
503
+ : undefined,
504
+ chapterIntent: input.chapterIntent,
505
+ contextPackage: input.contextPackage,
506
+ ruleStack: input.ruleStack,
507
+ validationFeedback: input.validationFeedback,
508
+ originalHooks: hooks,
509
+ originalSubplots: subplotBoard,
510
+ originalEmotionalArcs: emotionalArcs,
511
+ originalCharacterMatrix: characterMatrix,
512
+ });
513
+ const settlement = settleResult.settlement;
514
+ const runtimeStateArtifacts = await this.buildRuntimeStateArtifactsIfPresent(
515
+ input.bookDir,
516
+ settlement.runtimeStateDelta,
517
+ resolvedLanguage,
518
+ input.chapterNumber,
519
+ input.allowReapply,
520
+ );
521
+
522
+ return {
523
+ chapterNumber: input.chapterNumber,
524
+ title: input.title,
525
+ content: input.content,
526
+ wordCount: countChapterLength(
527
+ input.content,
528
+ resolvedLanguage === "en" ? "en_words" : "zh_chars",
529
+ ),
530
+ preWriteCheck: "",
531
+ postSettlement: settlement.postSettlement,
532
+ runtimeStateDelta: runtimeStateArtifacts?.resolvedDelta ?? settlement.runtimeStateDelta,
533
+ runtimeStateSnapshot: runtimeStateArtifacts?.snapshot ?? settlement.runtimeStateSnapshot,
534
+ updatedState: runtimeStateArtifacts?.currentStateMarkdown ?? settlement.updatedState,
535
+ updatedLedger: settlement.updatedLedger,
536
+ updatedHooks: runtimeStateArtifacts?.hooksMarkdown ?? settlement.updatedHooks,
537
+ chapterSummary: settlement.runtimeStateDelta
538
+ ? this.renderDeltaSummaryRow(settlement.runtimeStateDelta)
539
+ : settlement.chapterSummary,
540
+ updatedChapterSummaries: runtimeStateArtifacts?.chapterSummariesMarkdown,
541
+ updatedSubplots: settlement.updatedSubplots,
542
+ updatedEmotionalArcs: settlement.updatedEmotionalArcs,
543
+ updatedCharacterMatrix: settlement.updatedCharacterMatrix,
544
+ postWriteErrors: [],
545
+ postWriteWarnings: [],
546
+ tokenUsage: settleResult.usage,
547
+ };
548
+ }
549
+
550
+ private async settle(params: {
551
+ readonly book: BookConfig;
552
+ readonly genreProfile: GenreProfile;
553
+ readonly bookRules: BookRules | null;
554
+ readonly chapterNumber: number;
555
+ readonly title: string;
556
+ readonly content: string;
557
+ readonly currentState: string;
558
+ readonly ledger: string;
559
+ readonly hooks: string;
560
+ readonly chapterSummaries: string;
561
+ readonly subplotBoard: string;
562
+ readonly emotionalArcs: string;
563
+ readonly characterMatrix: string;
564
+ readonly volumeOutline: string;
565
+ readonly selectedEvidenceBlock?: string;
566
+ readonly chapterIntent?: string;
567
+ readonly contextPackage?: ContextPackage;
568
+ readonly ruleStack?: RuleStack;
569
+ readonly validationFeedback?: string;
570
+ readonly originalHooks: string;
571
+ readonly originalSubplots: string;
572
+ readonly originalEmotionalArcs: string;
573
+ readonly originalCharacterMatrix: string;
574
+ }): Promise<{
575
+ settlement: ReturnType<typeof parseSettlementOutput> & {
576
+ runtimeStateDelta?: RuntimeStateDelta;
577
+ runtimeStateSnapshot?: RuntimeStateSnapshot;
578
+ };
579
+ usage: TokenUsage;
580
+ }> {
581
+ // Phase 2a: Observer — extract all facts from the chapter
582
+ const resolvedLang = params.book.language ?? params.genreProfile.language;
583
+ const observerSystem = buildObserverSystemPrompt(params.book, params.genreProfile, resolvedLang);
584
+ const observerUser = buildObserverUserPrompt(params.chapterNumber, params.title, params.content, resolvedLang);
585
+
586
+ this.logInfo(resolvedLang, {
587
+ zh: `阶段 2a:提取第${params.chapterNumber}章事实`,
588
+ en: `Phase 2a: observing facts for chapter ${params.chapterNumber}`,
589
+ });
590
+ const observerResponse = await this.chat(
591
+ [
592
+ { role: "system", content: observerSystem },
593
+ { role: "user", content: observerUser },
594
+ ],
595
+ { temperature: 0.5 },
596
+ );
597
+ const observations = observerResponse.content;
598
+
599
+ // Phase 2b: Reflector — merge observations into truth files
600
+ this.logInfo(resolvedLang, {
601
+ zh: "阶段 2b:把观察结果回写到真相文件",
602
+ en: "Phase 2b: reflecting observations into truth files",
603
+ });
604
+ const settlerSystem = buildSettlerSystemPrompt(
605
+ params.book, params.genreProfile, params.bookRules, resolvedLang,
606
+ );
607
+ const governedControlBlock = params.chapterIntent && params.contextPackage && params.ruleStack
608
+ ? this.buildSettlerGovernedControlBlock(
609
+ params.chapterIntent,
610
+ params.contextPackage,
611
+ params.ruleStack,
612
+ resolvedLang,
613
+ )
614
+ : undefined;
615
+
616
+ const settlerUser = buildSettlerUserPrompt({
617
+ chapterNumber: params.chapterNumber,
618
+ title: params.title,
619
+ content: params.content,
620
+ currentState: this.capLegacyContext("current_state", params.currentState, LEGACY_WRITER_CONTEXT_BUDGET.currentState),
621
+ ledger: this.capLegacyContext("particle_ledger", params.ledger, LEGACY_WRITER_CONTEXT_BUDGET.ledger),
622
+ hooks: this.capLegacyContext("pending_hooks", params.hooks, LEGACY_WRITER_CONTEXT_BUDGET.hooks),
623
+ chapterSummaries: this.capLegacyContext(
624
+ "chapter_summaries",
625
+ params.chapterSummaries,
626
+ LEGACY_WRITER_CONTEXT_BUDGET.chapterSummaries,
627
+ ),
628
+ subplotBoard: this.capLegacyContext("subplot_board", params.subplotBoard, LEGACY_WRITER_CONTEXT_BUDGET.subplotBoard),
629
+ emotionalArcs: this.capLegacyContext("emotional_arcs", params.emotionalArcs, LEGACY_WRITER_CONTEXT_BUDGET.emotionalArcs),
630
+ characterMatrix: this.capLegacyContext(
631
+ "character_matrix",
632
+ params.characterMatrix,
633
+ LEGACY_WRITER_CONTEXT_BUDGET.characterMatrix,
634
+ ),
635
+ volumeOutline: this.capLegacyContext("volume_outline", params.volumeOutline, LEGACY_WRITER_CONTEXT_BUDGET.volumeOutline),
636
+ observations,
637
+ selectedEvidenceBlock: params.selectedEvidenceBlock,
638
+ governedControlBlock,
639
+ validationFeedback: params.validationFeedback,
640
+ });
641
+
642
+ const response = await this.chat(
643
+ [
644
+ { role: "system", content: settlerSystem },
645
+ { role: "user", content: settlerUser },
646
+ ],
647
+ { temperature: 0.3 },
648
+ );
649
+
650
+ let mergedSettlement: ReturnType<typeof parseSettlementOutput> & {
651
+ runtimeStateDelta?: RuntimeStateDelta;
652
+ runtimeStateSnapshot?: RuntimeStateSnapshot;
653
+ };
654
+ try {
655
+ const deltaOutput = parseSettlerDeltaOutput(response.content);
656
+ mergedSettlement = {
657
+ postSettlement: deltaOutput.postSettlement,
658
+ runtimeStateDelta: deltaOutput.runtimeStateDelta,
659
+ updatedState: "",
660
+ updatedLedger: "",
661
+ updatedHooks: "",
662
+ chapterSummary: "",
663
+ updatedSubplots: "",
664
+ updatedEmotionalArcs: "",
665
+ updatedCharacterMatrix: "",
666
+ };
667
+ } catch {
668
+ const settlement = parseSettlementOutput(response.content, params.genreProfile);
669
+ mergedSettlement = governedControlBlock
670
+ ? {
671
+ ...settlement,
672
+ updatedHooks: mergeTableMarkdownByKey(params.originalHooks, settlement.updatedHooks, [0]),
673
+ updatedSubplots: settlement.updatedSubplots
674
+ ? mergeTableMarkdownByKey(params.originalSubplots, settlement.updatedSubplots, [0])
675
+ : settlement.updatedSubplots,
676
+ updatedEmotionalArcs: settlement.updatedEmotionalArcs
677
+ ? mergeTableMarkdownByKey(params.originalEmotionalArcs, settlement.updatedEmotionalArcs, [0, 1])
678
+ : settlement.updatedEmotionalArcs,
679
+ updatedCharacterMatrix: settlement.updatedCharacterMatrix
680
+ ? mergeCharacterMatrixMarkdown(params.originalCharacterMatrix, settlement.updatedCharacterMatrix)
681
+ : settlement.updatedCharacterMatrix,
682
+ }
683
+ : settlement;
684
+ }
685
+
686
+ return {
687
+ settlement: mergedSettlement,
688
+ usage: response.usage,
689
+ };
690
+ }
691
+
692
+ async saveChapter(
693
+ bookDir: string,
694
+ output: WriteChapterOutput,
695
+ numericalSystem: boolean = true,
696
+ language: "zh" | "en" = "zh",
697
+ ): Promise<void> {
698
+ const chaptersDir = join(bookDir, "chapters");
699
+ const storyDir = join(bookDir, "story");
700
+ await mkdir(chaptersDir, { recursive: true });
701
+
702
+ const paddedNum = String(output.chapterNumber).padStart(4, "0");
703
+ const filename = `${paddedNum}_${this.sanitizeFilename(output.title)}.md`;
704
+
705
+ const heading = language === "en"
706
+ ? `# Chapter ${output.chapterNumber}: ${output.title}`
707
+ : `# 第${output.chapterNumber}章 ${output.title}`;
708
+ const chapterContent = [
709
+ heading,
710
+ "",
711
+ output.content,
712
+ ].join("\n");
713
+ const runtimeStateArtifacts = await this.resolveRuntimeStateArtifactsForOutput(
714
+ bookDir,
715
+ output,
716
+ language,
717
+ );
718
+
719
+ const writes: Array<Promise<void>> = [
720
+ writeFile(join(chaptersDir, filename), chapterContent, "utf-8"),
721
+ writeFile(join(storyDir, "current_state.md"), runtimeStateArtifacts?.currentStateMarkdown ?? output.updatedState, "utf-8"),
722
+ writeFile(join(storyDir, "pending_hooks.md"), runtimeStateArtifacts?.hooksMarkdown ?? output.updatedHooks, "utf-8"),
723
+ ];
724
+
725
+ if (runtimeStateArtifacts?.chapterSummariesMarkdown) {
726
+ writes.push(
727
+ writeFile(join(storyDir, "chapter_summaries.md"), runtimeStateArtifacts.chapterSummariesMarkdown, "utf-8"),
728
+ );
729
+ }
730
+
731
+ if (runtimeStateArtifacts?.snapshot ?? output.runtimeStateSnapshot) {
732
+ writes.push(saveRuntimeStateSnapshot(bookDir, runtimeStateArtifacts?.snapshot ?? output.runtimeStateSnapshot!));
733
+ }
734
+
735
+ if (numericalSystem) {
736
+ writes.push(
737
+ writeFile(join(storyDir, "particle_ledger.md"), output.updatedLedger, "utf-8"),
738
+ );
739
+ }
740
+
741
+ await Promise.all(writes);
742
+ }
743
+
744
+ private buildUserPrompt(params: {
745
+ readonly chapterNumber: number;
746
+ readonly storyBible: string;
747
+ readonly currentState: string;
748
+ readonly ledger: string;
749
+ readonly hooks: string;
750
+ readonly recentChapters: string;
751
+ readonly lengthSpec: LengthSpec;
752
+ readonly externalContext?: string;
753
+ readonly chapterSummaries: string;
754
+ readonly subplotBoard: string;
755
+ readonly emotionalArcs: string;
756
+ readonly characterMatrix: string;
757
+ readonly dialogueFingerprints?: string;
758
+ readonly relevantSummaries?: string;
759
+ readonly parentCanon?: string;
760
+ readonly language?: "zh" | "en";
761
+ }): string {
762
+ const currentState = this.capLegacyContext("current_state", params.currentState, LEGACY_WRITER_CONTEXT_BUDGET.currentState);
763
+ const ledger = this.capLegacyContext("particle_ledger", params.ledger, LEGACY_WRITER_CONTEXT_BUDGET.ledger);
764
+ const hooks = this.capLegacyContext("pending_hooks", params.hooks, LEGACY_WRITER_CONTEXT_BUDGET.hooks);
765
+ const chapterSummaries = this.capLegacyContext(
766
+ "chapter_summaries",
767
+ params.chapterSummaries,
768
+ LEGACY_WRITER_CONTEXT_BUDGET.chapterSummaries,
769
+ );
770
+ const subplotBoard = this.capLegacyContext("subplot_board", params.subplotBoard, LEGACY_WRITER_CONTEXT_BUDGET.subplotBoard);
771
+ const emotionalArcs = this.capLegacyContext("emotional_arcs", params.emotionalArcs, LEGACY_WRITER_CONTEXT_BUDGET.emotionalArcs);
772
+ const characterMatrix = this.capLegacyContext(
773
+ "character_matrix",
774
+ params.characterMatrix,
775
+ LEGACY_WRITER_CONTEXT_BUDGET.characterMatrix,
776
+ );
777
+ const storyBible = this.capLegacyContext("story_bible", params.storyBible, LEGACY_WRITER_CONTEXT_BUDGET.storyBible);
778
+ const parentCanon = params.parentCanon
779
+ ? this.capLegacyContext("parent_canon", params.parentCanon, LEGACY_WRITER_CONTEXT_BUDGET.parentCanon)
780
+ : undefined;
781
+ const contextBlock = params.externalContext
782
+ ? `\n## 外部指令\n以下是来自外部系统的创作指令,请在本章中融入:\n\n${params.externalContext}\n`
783
+ : "";
784
+
785
+ const ledgerBlock = ledger
786
+ ? `\n## 资源账本\n${ledger}\n`
787
+ : "";
788
+
789
+ const summariesBlock = chapterSummaries !== "(文件尚未创建)"
790
+ ? `\n## 章节摘要(全部历史章节压缩上下文)\n${chapterSummaries}\n`
791
+ : "";
792
+
793
+ const subplotBlock = subplotBoard !== "(文件尚未创建)"
794
+ ? `\n## 支线进度板\n${subplotBoard}\n`
795
+ : "";
796
+
797
+ const emotionalBlock = emotionalArcs !== "(文件尚未创建)"
798
+ ? `\n## 情感弧线\n${emotionalArcs}\n`
799
+ : "";
800
+
801
+ const matrixBlock = characterMatrix !== "(文件尚未创建)"
802
+ ? `\n## 角色交互矩阵\n${characterMatrix}\n`
803
+ : "";
804
+
805
+ const fingerprintBlock = params.dialogueFingerprints
806
+ ? `\n## 角色对话指纹\n${params.dialogueFingerprints}\n`
807
+ : "";
808
+
809
+ const relevantBlock = params.relevantSummaries
810
+ ? `\n## 相关历史章节摘要\n${params.relevantSummaries}\n`
811
+ : "";
812
+
813
+ const canonBlock = parentCanon
814
+ ? `\n## 正传正典参照(番外写作专用)
815
+ 本书是番外作品。以下正典约束不可违反,角色不得引用超出其信息边界的信息。
816
+ ${parentCanon}\n`
817
+ : "";
818
+ const lengthRequirementBlock = this.buildLengthRequirementBlock(params.lengthSpec, params.language ?? "zh");
819
+
820
+ if (params.language === "en") {
821
+ return `Write chapter ${params.chapterNumber}.
822
+ ${contextBlock}
823
+ ## Current State
824
+ ${currentState}
825
+ ${ledgerBlock}
826
+ ## Plot Threads
827
+ ${hooks}
828
+ ${summariesBlock}${subplotBlock}${emotionalBlock}${matrixBlock}${fingerprintBlock}${relevantBlock}${canonBlock}
829
+ ## Recent Chapters
830
+ ${params.recentChapters || "(This is the first chapter, no previous text)"}
831
+
832
+ ## Worldbuilding
833
+ ${storyBible}
834
+
835
+ ${lengthRequirementBlock}
836
+ - Output PRE_WRITE_CHECK first, then the chapter
837
+ - Output only PRE_WRITE_CHECK, CHAPTER_TITLE, and CHAPTER_CONTENT blocks`;
838
+ }
839
+
840
+ return `请续写第${params.chapterNumber}章。
841
+ ${contextBlock}
842
+ ## 当前状态卡
843
+ ${currentState}
844
+ ${ledgerBlock}
845
+ ## 伏笔池
846
+ ${hooks}
847
+ ${summariesBlock}${subplotBlock}${emotionalBlock}${matrixBlock}${fingerprintBlock}${relevantBlock}${canonBlock}
848
+ ## 最近章节
849
+ ${params.recentChapters || "(这是第一章,无前文)"}
850
+
851
+ ## 世界观设定
852
+ ${storyBible}
853
+
854
+ ${lengthRequirementBlock}
855
+ - 先输出写作自检表,再写正文
856
+ - 只需输出 PRE_WRITE_CHECK、CHAPTER_TITLE、CHAPTER_CONTENT 三个区块`;
857
+ }
858
+
859
+ private capLegacyContext(label: string, content: string, maxChars: number): string {
860
+ return capContextBlock(content, { label, maxChars });
861
+ }
862
+
863
+ private buildGovernedUserPrompt(params: {
864
+ readonly chapterNumber: number;
865
+ readonly chapterMemo: ChapterMemo;
866
+ readonly chapterIntentData?: ChapterIntent;
867
+ readonly contextPackage: ContextPackage;
868
+ readonly ruleStack: RuleStack;
869
+ readonly externalContext?: string;
870
+ readonly lengthSpec: LengthSpec;
871
+ readonly language?: "zh" | "en";
872
+ readonly varianceBrief?: string;
873
+ readonly selectedEvidenceBlock?: string;
874
+ }): string {
875
+ const language = params.language ?? "zh";
876
+ const contextSections = renderNarrativeSelectedContext(
877
+ params.contextPackage.selectedContext,
878
+ language,
879
+ );
880
+
881
+ const diagnosticLines = params.ruleStack.sections.diagnostic.length > 0
882
+ ? params.ruleStack.sections.diagnostic.join(", ")
883
+ : "none";
884
+
885
+ const lengthRequirementBlock = this.buildLengthRequirementBlock(params.lengthSpec, params.language ?? "zh");
886
+ const varianceBlock = params.varianceBrief
887
+ ? `\n${params.varianceBrief}\n`
888
+ : "";
889
+ const selectedEvidenceBlock = params.selectedEvidenceBlock
890
+ ? `\n${sanitizeNarrativeEvidenceBlock(params.selectedEvidenceBlock, language)}\n`
891
+ : "";
892
+ const chapterContextBlock = this.buildChapterContextBlock(params.externalContext, language);
893
+ const briefNarrative = renderMemoAsNarrativeBlock(params.chapterMemo, params.chapterIntentData, language);
894
+
895
+ if (params.language === "en") {
896
+ return `Write chapter ${params.chapterNumber}.
897
+
898
+ ${chapterContextBlock}
899
+
900
+ ${briefNarrative}
901
+
902
+ ## Selected Context
903
+ ${contextSections || "(none)"}
904
+ ${selectedEvidenceBlock}
905
+
906
+ ## Rule Stack
907
+ - Hard: ${params.ruleStack.sections.hard.join(", ") || "(none)"}
908
+ - Soft: ${params.ruleStack.sections.soft.join(", ") || "(none)"}
909
+ - Diagnostic: ${diagnosticLines}
910
+
911
+ ${varianceBlock}
912
+ ${lengthRequirementBlock}
913
+ - Output PRE_WRITE_CHECK first, then the chapter
914
+ - Output only PRE_WRITE_CHECK, CHAPTER_TITLE, and CHAPTER_CONTENT blocks`;
915
+ }
916
+
917
+ return `请续写第${params.chapterNumber}章。
918
+
919
+ ${chapterContextBlock}
920
+
921
+ ${briefNarrative}
922
+
923
+ ## 已选上下文
924
+ ${contextSections || "(无)"}
925
+ ${selectedEvidenceBlock}
926
+
927
+ ## 规则栈
928
+ - 硬护栏:${params.ruleStack.sections.hard.join("、") || "(无)"}
929
+ - 软约束:${params.ruleStack.sections.soft.join("、") || "(无)"}
930
+ - 诊断规则:${diagnosticLines}
931
+
932
+ ${varianceBlock}
933
+ ${lengthRequirementBlock}
934
+ - 先输出写作自检表,再写正文
935
+ - 只需输出 PRE_WRITE_CHECK、CHAPTER_TITLE、CHAPTER_CONTENT 三个区块`;
936
+ }
937
+
938
+ private buildChapterContextBlock(externalContext: string | undefined, language: "zh" | "en"): string {
939
+ const trimmed = externalContext?.trim();
940
+ if (!trimmed) return "";
941
+ if (language === "en") {
942
+ return `## Per-chapter user instruction (highest priority)
943
+ ${trimmed}
944
+
945
+ Obey this direct instruction for the current chapter. If it specifies a chapter title, use that title exactly in CHAPTER_TITLE. Keep continuity, but do not replace this instruction with the outline fallback.`;
946
+ }
947
+ return `## 本章用户指令(最高优先级)
948
+ ${trimmed}
949
+
950
+ 这是用户对当前章节的直接指令。若其中指定章节标题,CHAPTER_TITLE 必须原样使用该标题。保持连续性,但不要用卷纲兜底替换这条指令。`;
951
+ }
952
+
953
+ private joinGovernedEvidenceBlocks(blocks: ReturnType<typeof buildGovernedMemoryEvidenceBlocks> | undefined): string | undefined {
954
+ if (!blocks) {
955
+ return undefined;
956
+ }
957
+
958
+ const joined = [
959
+ blocks.titleHistoryBlock,
960
+ blocks.moodTrailBlock,
961
+ blocks.canonBlock,
962
+ blocks.hookDebtBlock,
963
+ blocks.hooksBlock,
964
+ blocks.summariesBlock,
965
+ blocks.volumeSummariesBlock,
966
+ ]
967
+ .filter((block): block is string => Boolean(block))
968
+ .join("\n");
969
+
970
+ return joined || undefined;
971
+ }
972
+
973
+ private buildSettlerGovernedControlBlock(
974
+ chapterIntent: string,
975
+ contextPackage: ContextPackage,
976
+ ruleStack: RuleStack,
977
+ language: "zh" | "en",
978
+ ): string {
979
+ const selectedContext = renderNarrativeSelectedContext(contextPackage.selectedContext, language)
980
+ .replace(/^### /gm, "- ");
981
+ const overrides = ruleStack.activeOverrides.length > 0
982
+ ? ruleStack.activeOverrides
983
+ .map((override) => `- ${override.from} -> ${override.to}: ${override.reason} (${override.target})`)
984
+ .join("\n")
985
+ : "- none";
986
+ const narrativeIntent = buildNarrativeIntentBrief(chapterIntent, language);
987
+
988
+ if (language === "en") {
989
+ return `\n## Chapter Control Inputs
990
+ ${narrativeIntent || "(none)"}
991
+
992
+ ### Selected Context
993
+ ${selectedContext || "- none"}
994
+
995
+ ### Rule Stack
996
+ - Hard guardrails: ${ruleStack.sections.hard.join(", ") || "(none)"}
997
+ - Soft constraints: ${ruleStack.sections.soft.join(", ") || "(none)"}
998
+ - Diagnostic rules: ${ruleStack.sections.diagnostic.join(", ") || "(none)"}
999
+
1000
+ ### Active Overrides
1001
+ ${overrides}\n`;
1002
+ }
1003
+
1004
+ return `\n## 本章控制输入
1005
+ ${narrativeIntent || "(无)"}
1006
+
1007
+ ### 已选上下文
1008
+ ${selectedContext || "- none"}
1009
+
1010
+ ### 规则栈
1011
+ - 硬护栏:${ruleStack.sections.hard.join("、") || "(无)"}
1012
+ - 软约束:${ruleStack.sections.soft.join("、") || "(无)"}
1013
+ - 诊断规则:${ruleStack.sections.diagnostic.join("、") || "(无)"}
1014
+
1015
+ ### 当前覆盖
1016
+ ${overrides}\n`;
1017
+ }
1018
+
1019
+ /**
1020
+ * Soft-check that the LLM's PRE_WRITE_CHECK output references the three
1021
+ * non-negotiable memo sections: 当前任务, 不要做, 章尾必须发生的改变.
1022
+ *
1023
+ * This is NOT a hard gate — the memo was already parse-validated in the
1024
+ * planner, and the writer prompt already tells the LLM to align to memo.
1025
+ * We only warn when the LLM skipped a section, so the chapter still ships.
1026
+ */
1027
+ private verifyPreWriteCheckAlignsWithMemo(
1028
+ preWriteCheck: string,
1029
+ chapterNumber: number,
1030
+ language: "zh" | "en",
1031
+ ): void {
1032
+ if (!preWriteCheck || preWriteCheck.trim().length === 0) {
1033
+ this.logWarn(language, {
1034
+ zh: `第${chapterNumber}章 PRE_WRITE_CHECK 为空,无法对齐 chapter_memo`,
1035
+ en: `Chapter ${chapterNumber} PRE_WRITE_CHECK is empty; cannot verify memo alignment`,
1036
+ });
1037
+ return;
1038
+ }
1039
+
1040
+ const missing: string[] = [];
1041
+ if (!preWriteCheck.includes("当前任务")) missing.push("当前任务");
1042
+ if (!preWriteCheck.includes("不要做")) missing.push("不要做");
1043
+ if (!preWriteCheck.includes("章尾")) missing.push("章尾必须发生的改变");
1044
+
1045
+ if (missing.length > 0) {
1046
+ this.logWarn(language, {
1047
+ zh: `第${chapterNumber}章 PRE_WRITE_CHECK 缺少 memo 章节检查:${missing.join("、")}`,
1048
+ en: `Chapter ${chapterNumber} PRE_WRITE_CHECK missing memo sections: ${missing.join(", ")}`,
1049
+ });
1050
+ }
1051
+ }
1052
+
1053
+ private buildLengthRequirementBlock(lengthSpec: LengthSpec, language: "zh" | "en"): string {
1054
+ if (language === "en") {
1055
+ return `Requirements:
1056
+ - Target length: ${lengthSpec.target} words
1057
+ - Acceptable range: ${lengthSpec.softMin}-${lengthSpec.softMax} words`;
1058
+ }
1059
+
1060
+ return `要求:
1061
+ - 目标字数:${lengthSpec.target}字
1062
+ - 允许区间:${lengthSpec.softMin}-${lengthSpec.softMax}字`;
1063
+ }
1064
+
1065
+ private async loadRecentChapters(
1066
+ bookDir: string,
1067
+ currentChapter: number,
1068
+ count = 1,
1069
+ ): Promise<string> {
1070
+ const chaptersDir = join(bookDir, "chapters");
1071
+ try {
1072
+ const files = await readdir(chaptersDir);
1073
+ const mdFiles = files
1074
+ .filter((f) => f.endsWith(".md") && !f.startsWith("index"))
1075
+ .sort()
1076
+ .slice(-count);
1077
+
1078
+ if (mdFiles.length === 0) return "";
1079
+
1080
+ const contents = await Promise.all(
1081
+ mdFiles.map(async (f) => {
1082
+ const content = await readFile(join(chaptersDir, f), "utf-8");
1083
+ return content;
1084
+ }),
1085
+ );
1086
+
1087
+ return contents.join("\n\n---\n\n");
1088
+ } catch {
1089
+ return "";
1090
+ }
1091
+ }
1092
+
1093
+ private async readFileOrDefault(path: string): Promise<string> {
1094
+ try {
1095
+ return await readFile(path, "utf-8");
1096
+ } catch {
1097
+ return "(文件尚未创建)";
1098
+ }
1099
+ }
1100
+
1101
+ /** Save new truth files (summaries, subplots, emotional arcs, character matrix). */
1102
+ async saveNewTruthFiles(
1103
+ bookDir: string,
1104
+ output: WriteChapterOutput,
1105
+ language: "zh" | "en" = "zh",
1106
+ ): Promise<void> {
1107
+ const storyDir = join(bookDir, "story");
1108
+ const writes: Array<Promise<void>> = [];
1109
+
1110
+ // Append chapter summary to chapter_summaries.md
1111
+ if (!output.runtimeStateDelta && output.updatedChapterSummaries) {
1112
+ writes.push(writeFile(
1113
+ join(storyDir, "chapter_summaries.md"),
1114
+ output.updatedChapterSummaries,
1115
+ "utf-8",
1116
+ ));
1117
+ } else if (!output.runtimeStateDelta && output.chapterSummary) {
1118
+ writes.push(this.appendChapterSummary(storyDir, output.chapterSummary, language));
1119
+ }
1120
+
1121
+ // Overwrite subplot board
1122
+ if (output.updatedSubplots) {
1123
+ writes.push(writeFile(join(storyDir, "subplot_board.md"), output.updatedSubplots, "utf-8"));
1124
+ }
1125
+
1126
+ // Overwrite emotional arcs
1127
+ if (output.updatedEmotionalArcs) {
1128
+ writes.push(writeFile(join(storyDir, "emotional_arcs.md"), output.updatedEmotionalArcs, "utf-8"));
1129
+ }
1130
+
1131
+ // Overwrite character matrix
1132
+ if (output.updatedCharacterMatrix) {
1133
+ writes.push(writeFile(join(storyDir, "character_matrix.md"), output.updatedCharacterMatrix, "utf-8"));
1134
+ }
1135
+
1136
+ await Promise.all(writes);
1137
+ }
1138
+
1139
+ private renderDeltaSummaryRow(delta: RuntimeStateDelta): string {
1140
+ if (!delta.chapterSummary) return "";
1141
+ const summary = delta.chapterSummary;
1142
+ const row = [
1143
+ summary.chapter,
1144
+ summary.title,
1145
+ summary.characters,
1146
+ summary.events,
1147
+ summary.stateChanges,
1148
+ summary.hookActivity,
1149
+ summary.mood,
1150
+ summary.chapterType,
1151
+ ].map((value) => String(value).replace(/\|/g, "\\|").trim()).join(" | ");
1152
+
1153
+ return `| ${row} |`;
1154
+ }
1155
+
1156
+ private normalizeRuntimeStateDeltaChapter(
1157
+ delta: RuntimeStateDelta,
1158
+ authoritativeChapterNumber: number,
1159
+ ): RuntimeStateDelta {
1160
+ const hookOps = delta.hookOps ?? {
1161
+ upsert: [],
1162
+ mention: [],
1163
+ resolve: [],
1164
+ defer: [],
1165
+ };
1166
+ let changed = delta.chapter !== authoritativeChapterNumber;
1167
+ const normalizedUpserts = hookOps.upsert.map((hook) => {
1168
+ const startChapter = Math.min(hook.startChapter, authoritativeChapterNumber);
1169
+ const lastAdvancedChapter = Math.min(hook.lastAdvancedChapter, authoritativeChapterNumber);
1170
+ if (startChapter !== hook.startChapter || lastAdvancedChapter !== hook.lastAdvancedChapter) {
1171
+ changed = true;
1172
+ }
1173
+ if (startChapter === hook.startChapter && lastAdvancedChapter === hook.lastAdvancedChapter) {
1174
+ return hook;
1175
+ }
1176
+ return {
1177
+ ...hook,
1178
+ startChapter,
1179
+ lastAdvancedChapter,
1180
+ };
1181
+ });
1182
+
1183
+ if (delta.chapterSummary?.chapter !== undefined && delta.chapterSummary.chapter !== authoritativeChapterNumber) {
1184
+ changed = true;
1185
+ }
1186
+ if (!changed) {
1187
+ return delta;
1188
+ }
1189
+
1190
+ return {
1191
+ ...delta,
1192
+ chapter: authoritativeChapterNumber,
1193
+ hookOps: {
1194
+ ...hookOps,
1195
+ upsert: normalizedUpserts,
1196
+ },
1197
+ chapterSummary: delta.chapterSummary
1198
+ ? {
1199
+ ...delta.chapterSummary,
1200
+ chapter: authoritativeChapterNumber,
1201
+ }
1202
+ : undefined,
1203
+ };
1204
+ }
1205
+
1206
+ private async buildRuntimeStateArtifactsIfPresent(
1207
+ bookDir: string,
1208
+ delta: RuntimeStateDelta | undefined,
1209
+ language: "zh" | "en",
1210
+ authoritativeChapterNumber?: number,
1211
+ allowReapply?: boolean,
1212
+ ): Promise<RuntimeStateArtifacts | null> {
1213
+ if (!delta) return null;
1214
+ const safeDelta = authoritativeChapterNumber === undefined
1215
+ ? delta
1216
+ : this.normalizeRuntimeStateDeltaChapter(delta, authoritativeChapterNumber);
1217
+ return buildRuntimeStateArtifacts({
1218
+ bookDir,
1219
+ delta: safeDelta,
1220
+ language,
1221
+ allowReapply,
1222
+ });
1223
+ }
1224
+
1225
+ private async resolveRuntimeStateArtifactsForOutput(
1226
+ bookDir: string,
1227
+ output: WriteChapterOutput,
1228
+ language: "zh" | "en",
1229
+ ): Promise<RuntimeStateArtifacts | null> {
1230
+ if (!output.runtimeStateDelta) return null;
1231
+ const safeDelta = this.normalizeRuntimeStateDeltaChapter(
1232
+ output.runtimeStateDelta,
1233
+ output.chapterNumber,
1234
+ );
1235
+ if (
1236
+ safeDelta === output.runtimeStateDelta
1237
+ && output.runtimeStateSnapshot
1238
+ && output.updatedChapterSummaries
1239
+ && output.updatedState
1240
+ && output.updatedHooks
1241
+ ) {
1242
+ return {
1243
+ snapshot: output.runtimeStateSnapshot,
1244
+ resolvedDelta: safeDelta,
1245
+ currentStateMarkdown: output.updatedState,
1246
+ hooksMarkdown: output.updatedHooks,
1247
+ chapterSummariesMarkdown: output.updatedChapterSummaries,
1248
+ };
1249
+ }
1250
+
1251
+ return buildRuntimeStateArtifacts({
1252
+ bookDir,
1253
+ delta: safeDelta,
1254
+ language,
1255
+ });
1256
+ }
1257
+
1258
+ private async appendChapterSummary(
1259
+ storyDir: string,
1260
+ summary: string,
1261
+ language: "zh" | "en",
1262
+ ): Promise<void> {
1263
+ const summaryPath = join(storyDir, "chapter_summaries.md");
1264
+ let existing = "";
1265
+ try {
1266
+ existing = await readFile(summaryPath, "utf-8");
1267
+ } catch {
1268
+ // File doesn't exist yet — start with header
1269
+ existing = language === "en"
1270
+ ? "# Chapter Summaries\n\n| Chapter | Title | Characters | Key Events | State Changes | Hook Activity | Mood | Chapter Type |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n"
1271
+ : "# 章节摘要\n\n| 章节 | 标题 | 出场人物 | 关键事件 | 状态变化 | 伏笔动态 | 情绪基调 | 章节类型 |\n|------|------|----------|----------|----------|----------|----------|----------|\n";
1272
+ }
1273
+
1274
+ // Extract only the data row(s) from the summary (skip header lines)
1275
+ const dataRows = summary
1276
+ .split("\n")
1277
+ .filter((line) =>
1278
+ line.startsWith("|")
1279
+ && !line.startsWith("| 章节")
1280
+ && !line.startsWith("| Chapter")
1281
+ && !line.startsWith("|--")
1282
+ && !line.startsWith("| ---"),
1283
+ )
1284
+ .join("\n");
1285
+
1286
+ if (dataRows) {
1287
+ // Deduplicate: remove existing rows with the same chapter number before appending
1288
+ const newChapterNums = new Set(
1289
+ dataRows.split("\n")
1290
+ .map((line) => line.split("|")[1]?.trim())
1291
+ .filter((ch) => ch && /^\d+$/.test(ch)),
1292
+ );
1293
+ const deduped = existing
1294
+ .split("\n")
1295
+ .filter((line) => {
1296
+ if (!line.startsWith("|")) return true;
1297
+ const chNum = line.split("|")[1]?.trim();
1298
+ return !chNum || !newChapterNums.has(chNum);
1299
+ })
1300
+ .join("\n");
1301
+ await writeFile(summaryPath, `${deduped.trimEnd()}\n${dataRows}\n`, "utf-8");
1302
+ }
1303
+ }
1304
+
1305
+ private buildStyleFingerprint(styleProfileRaw: string): string | undefined {
1306
+ if (!styleProfileRaw || styleProfileRaw === "(文件尚未创建)") return undefined;
1307
+ try {
1308
+ const profile = JSON.parse(styleProfileRaw);
1309
+ const lines: string[] = [];
1310
+ if (profile.avgSentenceLength) lines.push(`- 平均句长:${profile.avgSentenceLength}字`);
1311
+ if (profile.sentenceLengthStdDev) lines.push(`- 句长标准差:${profile.sentenceLengthStdDev}`);
1312
+ if (profile.avgParagraphLength) lines.push(`- 平均段落长度:${profile.avgParagraphLength}字`);
1313
+ if (profile.paragraphLengthRange) lines.push(`- 段落长度范围:${profile.paragraphLengthRange.min}-${profile.paragraphLengthRange.max}字`);
1314
+ if (profile.vocabularyDiversity) lines.push(`- 词汇多样性(TTR):${profile.vocabularyDiversity}`);
1315
+ if (profile.topPatterns?.length > 0) lines.push(`- 高频句式:${profile.topPatterns.join("、")}`);
1316
+ if (profile.rhetoricalFeatures?.length > 0) lines.push(`- 修辞特征:${profile.rhetoricalFeatures.join("、")}`);
1317
+ return lines.length > 0 ? lines.join("\n") : undefined;
1318
+ } catch {
1319
+ return undefined;
1320
+ }
1321
+ }
1322
+
1323
+
1324
+ /**
1325
+ * Extract dialogue fingerprints from recent chapters.
1326
+ * For each character with multiple dialogue lines, compute speaking style markers.
1327
+ */
1328
+ private extractDialogueFingerprints(recentChapters: string, _storyBible: string): string {
1329
+ if (!recentChapters) return "";
1330
+
1331
+ // Match dialogue patterns:
1332
+ // Chinese: "speaker说道:" or dialogue in ""「」
1333
+ // English: "dialogue," speaker said. or "dialogue."
1334
+ const dialogueRegex = /(?:(.{1,6})(?:说道|道|喝道|冷声道|笑道|怒道|低声道|大声道|喝骂道|冷笑道|沉声道|喊道|叫道|问道|答道)\s*[::]\s*["""「]([^"""」]+)["""」])|["""「]([^"""」]{2,})["""」]|"([^"]{2,})"/g;
1335
+
1336
+ const characterDialogues = new Map<string, string[]>();
1337
+ let match: RegExpExecArray | null;
1338
+
1339
+ while ((match = dialogueRegex.exec(recentChapters)) !== null) {
1340
+ const speaker = match[1]?.trim();
1341
+ const line = match[2] ?? match[3] ?? "";
1342
+ if (speaker && line.length > 1) {
1343
+ const existing = characterDialogues.get(speaker) ?? [];
1344
+ characterDialogues.set(speaker, [...existing, line]);
1345
+ }
1346
+ }
1347
+
1348
+ // Only include characters with >=2 dialogue lines
1349
+ const fingerprints: string[] = [];
1350
+ for (const [character, lines] of characterDialogues) {
1351
+ if (lines.length < 2) continue;
1352
+
1353
+ const avgLen = Math.round(lines.reduce((sum, l) => sum + l.length, 0) / lines.length);
1354
+ const isShort = avgLen < 15;
1355
+
1356
+ // Find frequent words/phrases (2+ occurrences)
1357
+ const wordCounts = new Map<string, number>();
1358
+ for (const line of lines) {
1359
+ // Extract 2-3 char segments as "words"
1360
+ for (let i = 0; i < line.length - 1; i++) {
1361
+ const bigram = line.slice(i, i + 2);
1362
+ wordCounts.set(bigram, (wordCounts.get(bigram) ?? 0) + 1);
1363
+ }
1364
+ }
1365
+ const frequentWords = [...wordCounts.entries()]
1366
+ .filter(([, count]) => count >= 2)
1367
+ .sort((a, b) => b[1] - a[1])
1368
+ .slice(0, 3)
1369
+ .map(([w]) => `「${w}」`);
1370
+
1371
+ // Detect style markers
1372
+ const markers: string[] = [];
1373
+ if (isShort) markers.push("短句为主");
1374
+ else markers.push("长句为主");
1375
+
1376
+ const questionCount = lines.filter((l) => l.includes("?") || l.includes("?")).length;
1377
+ if (questionCount > lines.length * 0.3) markers.push("反问多");
1378
+
1379
+ if (frequentWords.length > 0) markers.push(`常用${frequentWords.join("")}`);
1380
+
1381
+ fingerprints.push(`${character}:${markers.join(",")}`);
1382
+ }
1383
+
1384
+ return fingerprints.length > 0 ? fingerprints.join(";") : "";
1385
+ }
1386
+
1387
+ /**
1388
+ * Find relevant chapter summaries based on volume outline context.
1389
+ * Extracts character names and hook IDs from the current volume's outline,
1390
+ * then searches chapter summaries for matching entries.
1391
+ */
1392
+ private findRelevantSummaries(
1393
+ chapterSummaries: string,
1394
+ volumeOutline: string,
1395
+ chapterNumber: number,
1396
+ ): string {
1397
+ if (!chapterSummaries || chapterSummaries === "(文件尚未创建)") return "";
1398
+ if (!volumeOutline || volumeOutline === "(文件尚未创建)") return "";
1399
+
1400
+ // Extract character names from volume outline (Chinese name patterns)
1401
+ const nameRegex = /[\u4e00-\u9fff]{2,4}(?=[,、。:]|$)/g;
1402
+ const outlineNames = new Set<string>();
1403
+ let nameMatch: RegExpExecArray | null;
1404
+ while ((nameMatch = nameRegex.exec(volumeOutline)) !== null) {
1405
+ outlineNames.add(nameMatch[0]);
1406
+ }
1407
+
1408
+ // Extract hook IDs from volume outline
1409
+ const hookRegex = /H\d{2,}/g;
1410
+ const hookIds = new Set<string>();
1411
+ let hookMatch: RegExpExecArray | null;
1412
+ while ((hookMatch = hookRegex.exec(volumeOutline)) !== null) {
1413
+ hookIds.add(hookMatch[0]);
1414
+ }
1415
+
1416
+ if (outlineNames.size === 0 && hookIds.size === 0) return "";
1417
+
1418
+ // Search chapter summaries for matching rows
1419
+ const rows = chapterSummaries.split("\n").filter((line) =>
1420
+ line.startsWith("|") && !line.startsWith("| 章节") && !line.startsWith("|--") && !line.startsWith("| -"),
1421
+ );
1422
+
1423
+ const matchedRows = rows.filter((row) => {
1424
+ for (const name of outlineNames) {
1425
+ if (row.includes(name)) return true;
1426
+ }
1427
+ for (const hookId of hookIds) {
1428
+ if (row.includes(hookId)) return true;
1429
+ }
1430
+ return false;
1431
+ });
1432
+
1433
+ // Skip only the last chapter (its full text is already in context via loadRecentChapters)
1434
+ const filteredRows = matchedRows.filter((row) => {
1435
+ const chNumMatch = row.match(/\|\s*(\d+)\s*\|/);
1436
+ if (!chNumMatch) return true;
1437
+ const num = parseInt(chNumMatch[1]!, 10);
1438
+ return num < chapterNumber - 1;
1439
+ });
1440
+
1441
+ return filteredRows.length > 0 ? filteredRows.join("\n") : "";
1442
+ }
1443
+
1444
+ private sanitizeFilename(title: string): string {
1445
+ return title
1446
+ .replace(/[/\\?%*:|"<>]/g, "")
1447
+ .replace(/\s+/g, "_")
1448
+ .slice(0, 50);
1449
+ }
1450
+ }