@cloud411716/fancy-webnovel 1.0.46 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/client.js +369 -0
  2. package/cordis.patch.yml +4 -13
  3. package/docs/douyin-plugin.md +118 -0
  4. package/docs/material-library/README.md +404 -0
  5. package/docs/material-library/import.js +437 -0
  6. package/docs/material-library/prompt.md +370 -0
  7. package/docs/material-library/queries.surql +94 -0
  8. package/docs/material-library/sample.jsonl +14 -0
  9. package/docs/material-library/schema.surql +46 -0
  10. package/docs/mcp-profile.example.yml +24 -0
  11. package/docs/story-graph-design.md +602 -0
  12. package/docs/story-graph-validation.md +81 -0
  13. package/docs/workspace-mcp.md +284 -0
  14. package/index.js +26 -38
  15. package/lib/atomic-write.js +109 -0
  16. package/lib/chapter-order.js +100 -0
  17. package/lib/chinese-num.js +111 -0
  18. package/lib/confirmation.js +80 -0
  19. package/lib/db-init.js +123 -0
  20. package/lib/deployed-state.js +141 -0
  21. package/lib/direction-labels.js +265 -0
  22. package/lib/error-codes.js +104 -0
  23. package/lib/infra.js +594 -0
  24. package/lib/llm-wiki.js +109 -0
  25. package/lib/material-db.js +149 -0
  26. package/lib/path-guard.js +150 -0
  27. package/lib/projects.js +400 -0
  28. package/lib/scraper-utils.js +305 -0
  29. package/lib/surreal-server.js +338 -0
  30. package/lib/topic-decision.js +860 -0
  31. package/lib/topic-fields.js +251 -0
  32. package/lib/topic-schema.js +225 -0
  33. package/lib/workspace.js +501 -0
  34. package/package.json +62 -5
  35. package/plugins/fancy-analyze/analyze-definitions.md +519 -0
  36. package/plugins/fancy-analyze/command-input.js +65 -0
  37. package/plugins/fancy-analyze/index.js +2024 -0
  38. package/plugins/fancy-analyze/ingest-coverage.js +107 -0
  39. package/plugins/fancy-analyze/schema-analyze.md +627 -0
  40. package/plugins/fancy-analyze/schema-write.md +178 -0
  41. package/plugins/fancy-analyze/scripts/append-summary-chapter.js +612 -0
  42. package/plugins/fancy-analyze/scripts/split_source.js +292 -0
  43. package/plugins/fancy-analyze/summary-progress.js +495 -0
  44. package/plugins/fancy-analyze/templates.js +1179 -0
  45. package/plugins/fancy-bootstrap/index.js +313 -0
  46. package/plugins/fancy-bootstrap/scripts/init.js +177 -0
  47. package/plugins/fancy-bootstrap/scripts/path-utils.js +21 -0
  48. package/plugins/fancy-bootstrap/templates.js +71 -0
  49. package/plugins/fancy-db/index.js +172 -0
  50. package/plugins/fancy-douyin/deploy.js +134 -0
  51. package/plugins/fancy-douyin/index.js +551 -0
  52. package/plugins/fancy-douyin/prompts/01-analysis-prompt.md +84 -0
  53. package/plugins/fancy-douyin/scripts/crawl-note.cjs +194 -0
  54. package/plugins/fancy-douyin/scripts/lib.cjs +197 -0
  55. package/plugins/fancy-douyin/scripts/scan-account.cjs +128 -0
  56. package/plugins/fancy-douyin/scripts/start-chrome.cjs +203 -0
  57. package/plugins/fancy-douyin/state.js +197 -0
  58. package/plugins/fancy-douyin/templates.js +127 -0
  59. package/plugins/fancy-export/exporter.js +805 -0
  60. package/plugins/fancy-export/index.js +63 -0
  61. package/plugins/fancy-import/import-core.js +1118 -0
  62. package/plugins/fancy-import/import-driver.js +604 -0
  63. package/plugins/fancy-import/index.js +738 -0
  64. package/plugins/fancy-import/prompts/01-import-prompt.md +131 -0
  65. package/plugins/fancy-import/templates.js +58 -0
  66. package/plugins/fancy-material/exporter.js +153 -0
  67. package/plugins/fancy-material/genres.js +256 -0
  68. package/plugins/fancy-material/index.js +835 -0
  69. package/plugins/fancy-material/inventory.js +143 -0
  70. package/plugins/fancy-material/kinds.js +313 -0
  71. package/plugins/fancy-material/queue.js +55 -0
  72. package/plugins/fancy-material/scripts/query.js +254 -0
  73. package/plugins/fancy-material/templates.js +258 -0
  74. package/plugins/fancy-material/tool-deploy.js +138 -0
  75. package/plugins/fancy-material/validate.js +149 -0
  76. package/plugins/fancy-project/index.js +133 -0
  77. package/plugins/fancy-scan/existing-block.js +100 -0
  78. package/plugins/fancy-scan/index.js +708 -0
  79. package/plugins/fancy-scan/prompts/01-analysis-prompt.md +302 -0
  80. package/plugins/fancy-scan/scripts/append-block.js +725 -0
  81. package/plugins/fancy-scan/scripts/report-validate.js +824 -0
  82. package/plugins/fancy-scan/scripts/run-scan.js +667 -0
  83. package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +923 -0
  84. package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +469 -0
  85. package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +629 -0
  86. package/plugins/fancy-scan/templates/blocks.js +155 -0
  87. package/plugins/fancy-scan/templates/platforms.js +372 -0
  88. package/plugins/fancy-scan/templates/report-schema.js +90 -0
  89. package/plugins/fancy-scan/templates.js +694 -0
  90. package/plugins/fancy-scan/tool-deploy.js +335 -0
  91. package/plugins/fancy-scan/wait-turn.js +46 -0
  92. package/plugins/fancy-story-builder/db/README.md +106 -0
  93. package/plugins/fancy-story-builder/db/client.js +228 -0
  94. package/plugins/fancy-story-builder/db/commands.js +145 -0
  95. package/plugins/fancy-story-builder/db/ids.js +39 -0
  96. package/plugins/fancy-story-builder/db/import.js +798 -0
  97. package/plugins/fancy-story-builder/db/project.js +299 -0
  98. package/plugins/fancy-story-builder/db/query.js +361 -0
  99. package/plugins/fancy-story-builder/db/schema.js +417 -0
  100. package/plugins/fancy-story-builder/db/stage-sync.js +238 -0
  101. package/plugins/fancy-story-builder/deploy-manifest.js +239 -0
  102. package/plugins/fancy-story-builder/deslop-driver.js +821 -0
  103. package/plugins/fancy-story-builder/flesh-output.js +168 -0
  104. package/plugins/fancy-story-builder/index.js +6861 -0
  105. package/plugins/fancy-story-builder/patch-driver.js +390 -0
  106. package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +563 -0
  107. package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +602 -0
  108. package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +539 -0
  109. package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +225 -0
  110. package/plugins/fancy-story-builder/prompts/05-deslop-prompt.md +96 -0
  111. package/plugins/fancy-story-builder/purpose-template.md +83 -0
  112. package/plugins/fancy-story-builder/references/formulas-rhythm.json +302 -0
  113. package/plugins/fancy-story-builder/references/formulas-skeleton.json +402 -0
  114. package/plugins/fancy-story-builder/references/genre-profiles.json +3514 -0
  115. package/plugins/fancy-story-builder/references/glossary.md +594 -0
  116. package/plugins/fancy-story-builder/references/index.md +581 -0
  117. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/00-index.md +174 -0
  118. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/01-scene.md +150 -0
  119. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/02-chapter-workflow.md +99 -0
  120. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/03-opening-chapter.md +104 -0
  121. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/04-dialogue-mastery.md +274 -0
  122. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/05-genre-formulas.md +432 -0
  123. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/06-tone-ensemble.md +259 -0
  124. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/07-ledger-audit.md +222 -0
  125. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/08-diagnosis-revision.md +205 -0
  126. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/09-reading-power.md +135 -0
  127. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/10-deslop.md +129 -0
  128. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +98 -0
  129. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/01-upgrade-rhythm.md +300 -0
  130. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/02-arc-rhythm.md +168 -0
  131. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/03-highpoint-hooks.md +189 -0
  132. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/04-satisfaction.md +226 -0
  133. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/05-foreshadow-density.md +195 -0
  134. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/06-chapter-outline.md +226 -0
  135. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +181 -0
  136. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/00-index.md +100 -0
  137. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +243 -0
  138. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/02-worldbuilding.md +245 -0
  139. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +185 -0
  140. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/04-emotion-methods.md +286 -0
  141. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +187 -0
  142. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +91 -0
  143. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +209 -0
  144. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/02-packaging.md +135 -0
  145. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +305 -0
  146. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/04-conflict.md +328 -0
  147. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/05-quota.md +187 -0
  148. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +160 -0
  149. package/plugins/fancy-story-builder/rhythm-output.js +140 -0
  150. package/plugins/fancy-story-builder/schema-setting.json +1100 -0
  151. package/plugins/fancy-story-builder/schema-setting.md +232 -0
  152. package/plugins/fancy-story-builder/scripts/ai-patterns.js +619 -0
  153. package/plugins/fancy-story-builder/scripts/brief-validate.js +164 -0
  154. package/plugins/fancy-story-builder/scripts/check-guide-compliance.js +289 -0
  155. package/plugins/fancy-story-builder/scripts/content-quality.js +416 -0
  156. package/plugins/fancy-story-builder/scripts/deslop-validate.js +143 -0
  157. package/plugins/fancy-story-builder/scripts/error-classes.js +112 -0
  158. package/plugins/fancy-story-builder/scripts/flesh-validate.js +1312 -0
  159. package/plugins/fancy-story-builder/scripts/formula-engine.js +112 -0
  160. package/plugins/fancy-story-builder/scripts/graph-utils.js +1484 -0
  161. package/plugins/fancy-story-builder/scripts/label-coverage.js +171 -0
  162. package/plugins/fancy-story-builder/scripts/prompt-drift-check.js +441 -0
  163. package/plugins/fancy-story-builder/scripts/reading-power.js +298 -0
  164. package/plugins/fancy-story-builder/scripts/reference-log.js +110 -0
  165. package/plugins/fancy-story-builder/scripts/rhythm-validate.js +1312 -0
  166. package/plugins/fancy-story-builder/scripts/schema-pages.js +2668 -0
  167. package/plugins/fancy-story-builder/scripts/semantic-lint.js +381 -0
  168. package/plugins/fancy-story-builder/scripts/skeleton-quota.js +145 -0
  169. package/plugins/fancy-story-builder/scripts/skeleton-validate.js +1973 -0
  170. package/plugins/fancy-story-builder/scripts/stage-batches.js +1405 -0
  171. package/plugins/fancy-story-builder/scripts/stage-contract.js +429 -0
  172. package/plugins/fancy-story-builder/scripts/story-artifacts.js +933 -0
  173. package/plugins/fancy-story-builder/scripts/upstream-query.js +198 -0
  174. package/plugins/fancy-story-builder/scripts/v2/baseline.js +112 -0
  175. package/plugins/fancy-story-builder/scripts/v2/cast.js +517 -0
  176. package/plugins/fancy-story-builder/scripts/v2/context.js +434 -0
  177. package/plugins/fancy-story-builder/scripts/v2/contracts.js +493 -0
  178. package/plugins/fancy-story-builder/scripts/v2/core.js +419 -0
  179. package/plugins/fancy-story-builder/scripts/v2/engine.js +1068 -0
  180. package/plugins/fancy-story-builder/scripts/v2/export-markdown.js +794 -0
  181. package/plugins/fancy-story-builder/scripts/v2/genre.js +195 -0
  182. package/plugins/fancy-story-builder/scripts/v2/knowledge.js +321 -0
  183. package/plugins/fancy-story-builder/scripts/v2/macro.js +409 -0
  184. package/plugins/fancy-story-builder/scripts/v2/manifest.js +538 -0
  185. package/plugins/fancy-story-builder/scripts/v2/migrate.js +137 -0
  186. package/plugins/fancy-story-builder/scripts/v2/prompts.js +1335 -0
  187. package/plugins/fancy-story-builder/scripts/v2/rhythm.js +618 -0
  188. package/plugins/fancy-story-builder/scripts/v2/scale.js +843 -0
  189. package/plugins/fancy-story-builder/scripts/v2/snapshot.js +121 -0
  190. package/plugins/fancy-story-builder/scripts/v2/stages.js +273 -0
  191. package/plugins/fancy-story-builder/scripts/v2/writing.js +216 -0
  192. package/plugins/fancy-story-builder/scripts/writing-rules.js +167 -0
  193. package/plugins/fancy-story-builder/scripts/writing-validate.js +682 -0
  194. package/plugins/fancy-story-builder/skeleton-output.js +199 -0
  195. package/plugins/fancy-story-builder/stage-driver.js +1049 -0
  196. package/plugins/fancy-story-builder/storydb/README.md +271 -0
  197. package/plugins/fancy-story-builder/storydb/cli.js +458 -0
  198. package/plugins/fancy-story-builder/storydb/client.js +351 -0
  199. package/plugins/fancy-story-builder/storydb/compile.js +452 -0
  200. package/plugins/fancy-story-builder/storydb/context.js +288 -0
  201. package/plugins/fancy-story-builder/storydb/definitions.js +699 -0
  202. package/plugins/fancy-story-builder/storydb/driver.js +106 -0
  203. package/plugins/fancy-story-builder/storydb/dsh-tools.js +313 -0
  204. package/plugins/fancy-story-builder/storydb/export.js +175 -0
  205. package/plugins/fancy-story-builder/storydb/ids.js +31 -0
  206. package/plugins/fancy-story-builder/storydb/impact.js +170 -0
  207. package/plugins/fancy-story-builder/storydb/import.js +559 -0
  208. package/plugins/fancy-story-builder/storydb/mcp.js +99 -0
  209. package/plugins/fancy-story-builder/storydb/prompts.js +952 -0
  210. package/plugins/fancy-story-builder/storydb/stage-command.js +379 -0
  211. package/plugins/fancy-story-builder/storydb/stage.js +426 -0
  212. package/plugins/fancy-story-builder/storydb/store.js +891 -0
  213. package/plugins/fancy-story-builder/storydb/tools.js +592 -0
  214. package/plugins/fancy-story-builder/storydb/validation.js +183 -0
  215. package/plugins/fancy-story-builder/templates/v2/10-scale/scale-template.json +144 -0
  216. package/plugins/fancy-story-builder/templates/v2/20-macro/emotion-template.json +15 -0
  217. package/plugins/fancy-story-builder/templates/v2/20-macro/endings-template.json +16 -0
  218. package/plugins/fancy-story-builder/templates/v2/20-macro/lines-template.json +28 -0
  219. package/plugins/fancy-story-builder/templates/v2/20-macro/roles-template.json +10 -0
  220. package/plugins/fancy-story-builder/templates/v2/20-macro/story-template.json +19 -0
  221. package/plugins/fancy-story-builder/templates/v2/20-macro/volumes-template.json +24 -0
  222. package/plugins/fancy-story-builder/templates/v2/20-macro/world-template.json +24 -0
  223. package/plugins/fancy-story-builder/templates/v2/30-cast/characters-template.json +49 -0
  224. package/plugins/fancy-story-builder/templates/v2/30-cast/climaxes-template.json +14 -0
  225. package/plugins/fancy-story-builder/templates/v2/30-cast/conflicts-template.json +30 -0
  226. package/plugins/fancy-story-builder/templates/v2/30-cast/factions-template.json +18 -0
  227. package/plugins/fancy-story-builder/templates/v2/30-cast/foreshadows-template.json +29 -0
  228. package/plugins/fancy-story-builder/templates/v2/30-cast/information-template.json +29 -0
  229. package/plugins/fancy-story-builder/templates/v2/30-cast/items-template.json +13 -0
  230. package/plugins/fancy-story-builder/templates/v2/30-cast/locations-template.json +12 -0
  231. package/plugins/fancy-story-builder/templates/v2/30-cast/payoffs-template.json +16 -0
  232. package/plugins/fancy-story-builder/templates/v2/30-cast/relations-template.json +44 -0
  233. package/plugins/fancy-story-builder/templates/v2/40-rhythm/chapter-map-template.json +81 -0
  234. package/plugins/fancy-story-builder/templates/v2/40-rhythm/outline-template.json +162 -0
  235. package/plugins/fancy-story-builder/templates/v2/50-writing/ledger-template.json +46 -0
  236. package/plugins/fancy-story-builder/templates/v2/60-deslop/report-template.json +10 -0
  237. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//346/227/266/345/272/217/346/230/240/345/260/204/346/250/241/346/235/277.json +427 -0
  238. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/347/273/206/347/272/262/346/250/241/346/235/277.json +34 -0
  239. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/350/247/204/345/210/222/346/250/241/346/235/277.json +24 -0
  240. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//350/212/202/345/245/217/345/217/202/346/225/260/346/250/241/346/235/277.json +27 -0
  241. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//344/270/226/347/225/214/350/247/202/350/247/204/345/210/231/346/250/241/346/235/277.json +168 -0
  242. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/263/351/224/256/351/201/223/345/205/267/346/250/241/346/235/277.json +184 -0
  243. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/266/344/273/226/345/256/236/344/275/223/346/250/241/346/235/277.json +62 -0
  244. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/206/262/347/252/201/346/250/241/346/235/277.json +131 -0
  245. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/211/247/346/203/205/346/250/241/346/235/277.json +85 -0
  246. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/212/277/345/212/233/346/250/241/346/235/277.json +103 -0
  247. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/234/260/347/202/271/346/250/241/346/235/277.json +68 -0
  248. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//347/273/223/345/261/200/346/250/241/346/235/277.json +142 -0
  249. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//350/247/222/350/211/262/346/250/241/346/235/277.json +292 -0
  250. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/205/263/347/263/273/346/250/241/346/235/277.json +266 -0
  251. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/256/236/344/275/223/346/250/241/346/235/277.json +336 -0
  252. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/261/202/347/272/247/346/250/241/346/235/277.json +65 -0
  253. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//346/241/206/346/236/266/346/250/241/346/235/277.json +308 -0
  254. package/plugins/fancy-story-builder/templates.js +1446 -0
  255. package/plugins/fancy-story-builder/wiki-references.js +540 -0
  256. package/plugins/fancy-story-builder/writing-driver.js +1388 -0
  257. package/plugins/fancy-topic/index.js +1057 -0
  258. package/plugins/fancy-topic/prompts/01-exploration-prompt.md +115 -0
  259. package/plugins/fancy-topic/scripts/save-suggestion.js +219 -0
  260. package/plugins/fancy-topic/scripts/select-topic.js +317 -0
  261. package/plugins/fancy-topic/templates.js +476 -0
  262. package/plugins/fancy-topic/tool-deploy.js +200 -0
  263. package/commands/bootstrap/index.js +0 -164
  264. package/commands/bootstrap/init-project.js +0 -121
  265. package/commands/scan/index.js +0 -283
  266. package/commands/scan/platforms/fanqie.js +0 -146
  267. package/commands/scan/platforms/jinjiang.js +0 -143
  268. package/commands/scan/platforms/qidian.js +0 -102
  269. package/commands/scan/platforms/qimao.js +0 -127
  270. package/commands/scan/scraper.js +0 -195
  271. package/infra.js +0 -160
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * fancy-analyze split_source.js — 拆分长文本源文件
4
+ *
5
+ * 按"章"切分源文件,生成多份 .txt 分片,方便 LLM 分批读取。
6
+ *
7
+ * 切分逻辑:
8
+ * - 按 "第N章" / "第 N 章" / "Chapter N" / "chapter N" 等正则匹配
9
+ * - 每章单独输出到一个分片文件
10
+ * - 文件名格式: split_<3位零填充>_<章号>_<章标题>.txt
11
+ *
12
+ * CLI 模式 A(按书名查找):
13
+ * node split_source.js \
14
+ * --project-root <abs> \
15
+ * --book-name <书名>
16
+ *
17
+ * CLI 模式 B(直接指定源文件):
18
+ * node split_source.js \
19
+ * --source <abs-path-to-file> \
20
+ * --output-dir <abs-path-to-output-dir>
21
+ *
22
+ * 输出:
23
+ * <output-dir>/split_<NNN>.txt
24
+ */
25
+
26
+ import { readFileSync, writeFileSync, readdirSync, mkdirSync, statSync, unlinkSync } from 'fs';
27
+ import { join, dirname, resolve } from 'path';
28
+ import { fileURLToPath } from 'url';
29
+
30
+ import { cnToNumber } from '../../../lib/chinese-num.js';
31
+
32
+ const __dirname = dirname(fileURLToPath(import.meta.url));
33
+
34
+ // 多种章标记正则(按优先级)
35
+ const CHAPTER_PATTERNS = [
36
+ /^\s*第\s*([零〇一二两三四五六七八九十百千万亿\d]+)\s*章\s*[、\.\s ]*(.*)$/mgu,
37
+ /^\s*Chapter\s*(\d+)\s*[、\.\s:]*(.*)$/mgu,
38
+ /^\s*chapter\s*(\d+)\s*[、\.\s:]*(.*)$/mgu,
39
+ /^\s*CHAPTER\s*(\d+)\s*[、\.\s:]*(.*)$/mgu,
40
+ ];
41
+
42
+ /**
43
+ * 中文数字 → 阿拉伯数字。
44
+ *
45
+ * 2026-10-27:这份转换原本是本文件私有的,而且比仓库里其它地方弱 ——
46
+ * 只认到"千"、没有"两"、算法也不对("十二万"会算成 20010)。
47
+ * 现在统一走 lib/chinese-num.js 的 cnToNumber,全仓库一个口径。
48
+ */
49
+ const CN_DIGIT_CHARS = '零〇一二两三四五六七八九十百千万亿';
50
+
51
+ function findChapterStarts(text) {
52
+ const starts = [];
53
+ for (const pat of CHAPTER_PATTERNS) {
54
+ for (const m of text.matchAll(pat)) {
55
+ const numStr = m[1];
56
+ const num = cnToNumber(numStr);
57
+ if (num !== null && num > 0) {
58
+ const title = m[2] ? m[2].trim() : '';
59
+ starts.push({ pos: m.index, num, title });
60
+ }
61
+ }
62
+ }
63
+ // 去重 + 排序
64
+ const seen = new Set();
65
+ const unique = [];
66
+ for (const item of starts.sort((a, b) => a.pos - b.pos)) {
67
+ const key = `${item.num}_${Math.floor(item.pos / 100)}`;
68
+ if (!seen.has(key)) {
69
+ seen.add(key);
70
+ unique.push(item);
71
+ }
72
+ }
73
+ return unique;
74
+ }
75
+
76
+ function safeFilename(name) {
77
+ // 去掉所有标点符号和空白字符
78
+ return name.replace(/[\\/:*?"<>|\s.,,、。;;‘’""【】《》()()!!??_]/g, '').substring(0, 30);
79
+ }
80
+
81
+ function outputJson(data) {
82
+ console.log(JSON.stringify(data, null, 2));
83
+ }
84
+
85
+ /** 单个分片的最小字符数。小于这个值的分片对 LLM 没有意义,
86
+ * 而且 <1000 会让"第 0 章""abc"这类脏参数变成 0 → 无限循环。 */
87
+ const MIN_CHARS_PER_SPLIT = 1000;
88
+
89
+ /**
90
+ * 本脚本产出的分片文件名。
91
+ *
92
+ * 清理旧分片时**只删这些**,不能删目录下所有 .txt ——
93
+ * 输出目录有可能就是用户的资料目录/旧稿目录,
94
+ * 删光 .txt 等于替用户把资料删了。
95
+ */
96
+ const OWNED_SPLIT_FILE_RE = /^(\d{3}\.txt|第\d+章.*\.txt)$/;
97
+
98
+ function main() {
99
+ const args = process.argv.slice(2);
100
+ let sourcePath = null;
101
+ let projectRoot = null;
102
+ let outputDir = null;
103
+ let maxCharsPerSplit = 80000;
104
+
105
+ // 简单参数解析
106
+ for (let i = 0; i < args.length; i++) {
107
+ if (args[i] === '--source' && i + 1 < args.length) {
108
+ sourcePath = resolve(args[++i]);
109
+ } else if (args[i] === '--project-root' && i + 1 < args.length) {
110
+ projectRoot = resolve(args[++i]);
111
+ } else if (args[i] === '--book-name' && i + 1 < args.length) {
112
+ // 从项目根找书名的逻辑在调用方处理,这里不重复
113
+ i++;
114
+ } else if (args[i] === '--output-dir' && i + 1 < args.length) {
115
+ outputDir = resolve(args[++i]);
116
+ } else if (args[i] == '--max-chars-per-split' && i + 1 < args.length) {
117
+ const raw = args[++i];
118
+ const n = Number(raw);
119
+ // 必须严格校验:旧实现用 parseInt,'0abc' 会变成 0,
120
+ // 而分片循环是 `for (let i = 0; i < total; i += chunkSize)` ——
121
+ // chunkSize=0 就是**无限循环**,一直写文件直到磁盘满。
122
+ // 负数同理(i 递减)。NaN 则写出空文件。
123
+ if (!Number.isInteger(n) || n < MIN_CHARS_PER_SPLIT) {
124
+ outputJson({
125
+ ok: false,
126
+ error: {
127
+ code: 6,
128
+ code_name: 'EC_BAD_USAGE',
129
+ message: '--max-chars-per-split 必须是 >= ' + MIN_CHARS_PER_SPLIT + ' 的整数(当前:' + raw + ')'
130
+ }
131
+ });
132
+ process.exit(6);
133
+ }
134
+ maxCharsPerSplit = n;
135
+ }
136
+ }
137
+
138
+ // 至少需要 source + output-dir
139
+ if (!sourcePath || !outputDir) {
140
+ outputJson({
141
+ ok: false,
142
+ error: { code: 6, code_name: 'EC_BAD_USAGE', message: '必须传 --source 和 --output-dir' }
143
+ });
144
+ process.exit(6);
145
+ }
146
+
147
+ // 检查源文件
148
+ let stat;
149
+ try {
150
+ stat = statSync(sourcePath);
151
+ } catch {
152
+ outputJson({ ok: false, error: { code: 1, code_name: 'EC_SOURCE_MISSING', message: `源文件不存在: ${sourcePath}` } });
153
+ process.exit(1);
154
+ }
155
+ if (!stat.isFile()) {
156
+ outputJson({ ok: false, error: { code: 1, code_name: 'EC_SOURCE_MISSING', message: `源文件不是文件: ${sourcePath}` } });
157
+ process.exit(1);
158
+ }
159
+
160
+ // 输出目录不能就是源文件所在的目录 ——
161
+ // 清理旧分片会把源文件一起删掉(源文件通常也是 .txt),
162
+ // 然后 readFileSync 失败,用户的旧稿就这么没了。
163
+ const sourceDir = dirname(sourcePath);
164
+ if (sourceDir === outputDir) {
165
+ outputJson({
166
+ ok: false,
167
+ error: {
168
+ code: 6,
169
+ code_name: 'EC_BAD_USAGE',
170
+ message: '--output-dir 不能和源文件在同一目录(会把源文件当旧分片删掉):' + outputDir
171
+ }
172
+ });
173
+ process.exit(6);
174
+ }
175
+
176
+ // 创建输出目录
177
+ mkdirSync(outputDir, { recursive: true });
178
+
179
+ // 清掉上一次拆分留下的分片。
180
+ //
181
+ // 这个目录是 llm_wiki 的 ingest 输入,"重新分析同一本书"时如果不清,
182
+ // 上一版的旧分片会一直躺在里面 —— 目录内容和源文件对不上,
183
+ // 用户以为在分析新版,拿到的却是新旧混合的产物(实测:源文件从
184
+ // 3 章改成 1 章后重跑,目录里仍然是 3 个文件)。
185
+ //
186
+ // **只删本脚本产出的文件**(OWNED_SPLIT_FILE_RE):
187
+ // 旧实现删的是目录下**所有** .txt —— 输出目录如果是用户的资料目录,
188
+ // 这一删就是替用户把资料全删了。
189
+ try {
190
+ for (const f of readdirSync(outputDir)) {
191
+ if (OWNED_SPLIT_FILE_RE.test(f)) {
192
+ try { unlinkSync(join(outputDir, f)); } catch { /* 单个删不掉就跳过 */ }
193
+ }
194
+ }
195
+ } catch {
196
+ // 目录读不到就当没有旧分片
197
+ }
198
+
199
+ // 读取文件并统一转为 UTF-8
200
+ let text;
201
+ try {
202
+ const raw = readFileSync(sourcePath); // 读取原始字节
203
+ // 检测是否为有效 UTF-8:尝试用 UTF-8 解码
204
+ try {
205
+ const decoder = new TextDecoder('utf-8', { fatal: true });
206
+ text = decoder.decode(raw);
207
+ } catch {
208
+ // UTF-8 解码失败(fatal),说明是 GBK 编码
209
+ text = new TextDecoder('gbk').decode(raw);
210
+ }
211
+ } catch (err) {
212
+ outputJson({ ok: false, error: { code: 7, code_name: 'EC_READ_ERROR', message: `读取源文件失败: ${err.message}` } });
213
+ process.exit(7);
214
+ }
215
+
216
+ if (!text.trim()) {
217
+ outputJson({ ok: false, error: { code: 2, code_name: 'EC_SOURCE_EMPTY', message: '来源文件为空' } });
218
+ process.exit(2);
219
+ }
220
+
221
+ // 找章节起始
222
+ const chapterStarts = findChapterStarts(text);
223
+ const written = [];
224
+
225
+ if (chapterStarts.length === 0) {
226
+ // 没找到章标记 → 按 maxCharsPerSplit 切分
227
+ const chunkSize = maxCharsPerSplit;
228
+ const total = text.length;
229
+ for (let i = 0; i < total; i += chunkSize) {
230
+ const partNum = written.length + 1;
231
+ const partPath = join(outputDir, `${String(partNum).padStart(3, '0')}.txt`);
232
+ writeFileSync(partPath, text.slice(i, i + chunkSize), 'utf-8');
233
+ written.push(`${String(partNum).padStart(3, '0')}.txt`);
234
+ }
235
+ } else {
236
+ // 按章节切分
237
+ for (let i = 0; i < chapterStarts.length; i++) {
238
+ const { pos, num, title } = chapterStarts[i];
239
+ const end = i + 1 < chapterStarts.length ? chapterStarts[i + 1].pos : text.length;
240
+ // 去掉开头的空白行和章节标题行(如"第一章 xxx")
241
+ const rawText = text.slice(pos, end);
242
+ const lines = rawText.split('\n');
243
+ let startIndex = 0;
244
+ for (let j = 0; j < lines.length; j++) {
245
+ if (lines[j].trim() === '') {
246
+ startIndex = j + 1;
247
+ } else {
248
+ break;
249
+ }
250
+ }
251
+ // 跳过章节标题行(第一行非空白内容)
252
+ const chapterText = lines.slice(startIndex + 1).join('\n');
253
+ const safeTitle = safeFilename(title);
254
+ let base = '';
255
+ if (safeTitle) {
256
+ base = `第${num}章-${safeTitle}`;
257
+ } else {
258
+ base = `第${num}章`;
259
+ }
260
+ // 超长的章要按 maxCharsPerSplit **二次切分** ——
261
+ // 旧实现只看 chapterStarts 的章边界,一个 20 万字的章会原样写成一个
262
+ // 分片,仍然远超 LLM 上下文,ingest 时照样被截断。
263
+ const pieces = [];
264
+ if (chapterText.length <= maxCharsPerSplit) {
265
+ pieces.push(chapterText);
266
+ } else {
267
+ for (let j = 0; j < chapterText.length; j += maxCharsPerSplit) {
268
+ pieces.push(chapterText.slice(j, j + maxCharsPerSplit));
269
+ }
270
+ }
271
+ for (let k = 0; k < pieces.length; k++) {
272
+ const suffix = pieces.length > 1 ? '-' + String(k + 1) : '';
273
+ const filename = base + suffix + '.txt';
274
+ const partPath = join(outputDir, filename);
275
+ writeFileSync(partPath, pieces[k], 'utf-8');
276
+ written.push(filename);
277
+ }
278
+ }
279
+ }
280
+
281
+ outputJson({
282
+ ok: true,
283
+ operation: 'split-source',
284
+ source_path: sourcePath,
285
+ source_chars: text.length,
286
+ chapter_count: chapterStarts.length,
287
+ split_files: written,
288
+ split_count: written.length,
289
+ });
290
+ }
291
+
292
+ main();