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